Interface:本質、Android向けJavaとKotlinのコントラクト

著者: IT Sectr 公開日: 2026-02-18 読了時間: 9 分

Interfaceは、クラスが実装すべき抽象メソッドのセットを定義するコントラクトです。JavaとKotlinにおいて、インターフェースは抽象化とポリモーフィズムの主要なメカニズムです。Java 8+では、インターフェースにdefaultメソッドとstaticメソッドを含めることができ、Kotlinではデフォルト実装を含めることができます。Google Android Developers(2025)によると、Androidプロジェクトの90%でインターフェースを使用してアーキテクチャ層(リポジトリ、UseCase、サービス)を定義しています。

重要なポイント

  • Interfaceは、実装なしでメソッドシグネチャを定義する抽象型(Java 8以前)
  • Implementsは、クラスをインターフェースに結び付けるキーワード。クラスは複数のインターフェースを実装できる
  • Default methodは、Java 8で後方互換性のために追加された、Javaインターフェースの実装付きメソッド
  • Kotlin interfaceはゲッター付きプロパティとメソッド実装をサポートし、多くのシナリオでabstract classを置き換える
  • Markup interfaceは、マーカーとして使用される空のインターフェース(Serializable、Cloneable、RandomAccess)

JavaとKotlinにおけるInterfaceとは?

Interfaceは、抽象メソッド、定数、デフォルトメソッドを含む参照型です。インターフェースを実装するクラスは、そのすべての抽象メソッドの実装を提供する必要があります。Javaでは、インターフェースは状態(インスタンスフィールド)を持つことができません。Kotlinもこの制限に従いますが、アクセサ付きのプロパティをサポートしています。

Java
public interface Repository {
    T findById(Long id);
    List findAll();
    T save(T entity);
    void deleteById(Long id);
    
    default long count() {
        return findAll().size();
    }
}

@Entity
public class UserEntity {
    private Long id;
    private String email;
    // ゲッターとセッター
}

public class UserRepositoryImpl implements Repository {
    private final EntityManager em;
    
    public UserEntity findById(Long id) {
        return em.find(UserEntity.class, id);
    }
    
    public List findAll() {
        return em.createQuery("FROM UserEntity", UserEntity.class)
                .getResultList();
    }
    // その他のメソッド
}

Repository<T>はCRUDメソッドを持つジェネリックインターフェースです。デフォルトメソッドcount()は、オーバーライド可能なデフォルト実装を提供します。UserRepositoryImplは、データアクセスにEntityManagerを使用してインターフェースを実装します。このアプローチにより、実際のデータベースなしでインターフェースのモックを通じてデータ層をテストできます。

Interface vs abstract class:どちらを選ぶべきか

インターフェースと抽象クラスの選択は、共有状態の有無と型間の関係に依存します。インターフェースはコントラクト(クラスができること)を定義し、抽象クラスは共通の実装(クラスが何であるか)を定義します。

基準InterfaceAbstract Class
状態(フィールド)static final定数のみはい、任意のフィールド
コンストラクタなしあり
多重継承可(implements)不可(extendsは一つ)
アクセス修飾子public(Java 8-)、defaultメソッドすべて(private、protected、public)
使用するタイミング無関係なクラス用のコントラクト関連クラス用の共通基底

Clean Architectureでは、インターフェースは内側の層(domain)に配置され、実装は外側の層(data)に配置されます。これにより、依存関係ルール(外側の層は内側の層に依存するが、その逆は不可)を維持できます。抽象クラスは、テンプレートメソッド(Template Methodパターン)によく使用されます。

KotlinのInterface:プロパティとデリゲート

KotlinのインターフェースはJavaよりも柔軟です。抽象プロパティを宣言し、メソッド実装を提供できます。Javaとは異なり、Kotlinはbyキーワードによるデリゲーションをサポートしており、Delegateパターンを実装する際のボイラープレートを大幅に削減します。

Kotlin
interface ApiService {
    val baseUrl: String  // 抽象プロパティ
    
    suspend fun fetchData(): Result>
    
    fun getEndpoint(path: String): String {
        return "$baseUrl/$path"  // デフォルト実装
    }
}

class RetrofitApiService(
    override val baseUrl: String
) : ApiService {
    private val client = Retrofit.Builder()
        .baseUrl(baseUrl)
        .build()
    
    override suspend fun fetchData(): Result> {
        // リクエストの実装
    }
}

// byによるデリゲーション
interface Logger {
    fun log(message: String)
}

class ConsoleLogger : Logger {
    override fun log(message: String) = println(message)
}

class UserService(logger: Logger) : Logger by logger

UserServiceは、デリゲーション(by)を介してLoggerインターフェースを実装します。すべてのlog()呼び出しは、ラッパーメソッドを記述せずにloggerオブジェクトに転送されます。これは、Javaでは5〜10行のボイラープレートコードを要するコンポジションの例です。

Clean Architecture AndroidにおけるInterface

Clean Architecture for Androidは、アプリケーションを明確に層に分割します。インターフェースは層間の境界として機能します。DomainはリポジトリとUseCaseのインターフェースを定義し、Dataは実装を提供します。これにより、ビジネスロジックを変更せずに実装を置き換えることができ、RoomからFirebase、またはRESTからGraphQLへの移行時の重要な利点となります。

Kotlin
// Domain layer — インターフェース(コントラクト)
interface UserRepository {
    suspend fun getUser(id: String): User
    suspend fun updateUser(user: User)
}

// Domain layer — use case(インターフェースに依存)
class GetUserUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(id: String): Result {
        return runCatching { repository.getUser(id) }
    }
}

// Data layer — 実装
class UserRepositoryImpl(
    private val api: UserApi,
    private val dao: UserDao
) : UserRepository {
    override suspend fun getUser(id: String): User {
        val cached = dao.getUser(id)
        if (cached != null) return cached
        val remote = api.fetchUser(id)
        dao.insertUser(remote)
        return remote
    }
    // updateUser...
}

UserRepositoryはdomain層のインターフェースです。GetUserUseCaseは実装ではなくインターフェースに依存します。data層のUserRepositoryImplは、APIとローカルデータベースを組み合わせてインターフェースを実装します。このアーキテクチャにより、データベースやネットワークを設定せずにモックリポジトリでUseCasesをテストできます。

Java 8+のdefaultメソッドとstaticメソッド

defaultメソッドは、後方互換性を壊さずにインターフェースを進化的に拡張するためにJava 8で追加されました。ArrayListがCollectionに追加された新しいstream()メソッドを実装していなくても、古いコードは引き続き機能します。インターフェースのstaticメソッドは、インターフェースに関連するユーティリティとして機能し、ユーティリティクラスの代替となります。

Java
public interface Vehicle {
    void start();
    void stop();
    
    default void honk() {
        System.out.println("Beep!");
    }
    
    static Vehicle of(String type) {
        if ("car".equals(type)) return new Car();
        return new Bicycle();
    }
    
    // 定数
    String CATEGORY = "transport";
}

defaultメソッドはdiamond problemを解決します。クラスが同じdefaultメソッドを持つ2つのインターフェースを実装する場合、コンパイラは明示的なオーバーライドを要求します。staticメソッドは、インターフェース名を介して呼び出されます — Vehicle.of("car")のように、インスタンスなしで呼び出せます。

インターフェース設計のよくある間違い

インターフェース設計の誤りは、脆弱なコード、テストの複雑さ、SOLID違反につながります。3つの一般的な問題を見てみましょう。

Interface Pollution — 1つのインターフェースに多すぎるメソッド

15+のメソッドを含むインターフェースは、Interface Segregation Principle(ISP)に違反します。例として、10+のメソッドを持つ古いjava.util.Dictionaryがあります。解決策:複数の小さなインターフェース(ReadableRepository、WritableRepository、SearchableRepository)に分割します。クライアント(サービス)は必要なメソッドのみに依存します。

過剰な抽象化 — すべてのクラスにインターフェース

ポリモーフィズムの真の必要性なしにすべてのクラスにインターフェースを作成することは、Interface overkillアンチパターンです。兆候:インターフェースに実装が1つだけで、プロジェクトに代替を追加する計画がない。解決策:2番目の実装オプションが出現したとき、またはテスト用のモックが必要になったときにのみインターフェースを追加します。

よくある質問

Javaでインターフェースと抽象クラスの違いは?

Interfaceはコントラクト(メソッドシグネチャ)のみを定義し、状態を持てず、多重継承をサポートします。Abstract classはフィールド、コンストラクタ、実装済みメソッドを含められますが、クラスが継承できる抽象クラスは1つだけです。Java 8以降、インターフェースはdefaultメソッドとstaticメソッドを獲得し、ギャップを縮めました。

インターフェースは別のインターフェースを継承できますか?

はい、JavaとKotlinではインターフェースは継承をサポートします。public interface AdvancedRepository<T> extends Repository<T>, Pageableは、他の2つを組み合わせたインターフェースです。AdvancedRepositoryを実装するクラスは、両方の親インターフェースのすべてのメソッドを実装する必要があります。多重継承はインターフェースのみに許可されています。

Javaの関数型インターフェースとは?

関数型インターフェースは、単一の抽象メソッド(SAM — Single Abstract Method)を持つインターフェースです。@FunctionalInterfaceアノテーションがこの制限を保証します。例:Runnable、Callable、Comparator、Consumer。関数型インターフェースはJava 8ラムダ式の基盤です:() -> System.out.println()はRunnableを実装します。

Javaインターフェースにdefaultメソッドが必要な理由は?

defaultメソッドにより、すべての実装クラスを変更せずにインターフェースに新しいメソッドを追加できます。例えば、Java 8はstream()をCollectionにdefaultメソッドとして追加しました。このメカニズムなしでは、foreach()、stream()、その他のメソッドのためにJDKの数千のクラスを変更する必要がありました。Defaultは後方互換性のある拡張方法です。

Kotlinは多重継承の問題をどのように解決しますか?

Kotlinはクラスの多重継承を禁止していますが、インターフェースの多重実装は許可しています。2つのインターフェースが同じシグネチャとデフォルト実装を持つメソッドを持つ場合、コンパイラはsuper<InterfaceName>.method()呼び出しによる明示的なオーバーライドを要求します。これにより、コンパイルレベルでdiamond problemが解決されます。

まとめ

  • Interfaceは、クラスが実装すべきメソッドを定義するコントラクト。JavaとKotlinにおけるポリモーフィズムの主要メカニズム
  • Java 8+はインターフェースにdefaultメソッドとstaticメソッドを追加し、抽象クラスとのギャップを縮めた
  • Kotlin interfacesは抽象プロパティ、デフォルト実装、byによるデリゲーションをサポート
  • Clean ArchitectureはインターフェースをDomain層とData層の境界として使用
  • Interface Segregation Principleは大きなインターフェースを専門化されたものに分割することを要求
  • 関数型インターフェース(Single Abstract Method)はラムダ式とStream APIの基盤
  • 推奨:2番目の実装が出現したとき、またはテスト用のモックが必要になったときにインターフェースを追加する

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください