1. 概要

このチュートリアルでは、Kotlinでインターフェースを定義および実装する方法について説明します。

また、クラスによって複数のインターフェイスを実装する方法についても見ていきます。 これは確かに競合を引き起こす可能性があり、Kotlinがそれらを解決する必要があるメカニズムを学習します。

2. Kotlinのインターフェース

インターフェイスは、オブジェクト指向プログラミングのクラスの説明またはコントラクトを提供する方法です。 それらには、プログラミング言語に応じて、抽象的または具体的な方法でプロパティと関数が含まれる場合があります。 Kotlinのインターフェースの詳細を見ていきます。

Kotlinのインターフェースは、Javaのような他の多くの言語のインターフェースに似ています。 しかし、それらには特定の構文があります。次のいくつかのサブセクションでそれらを確認しましょう。

2.1. インターフェイスの定義

Kotlinで最初のインターフェースを定義することから始めましょう:

interface SimpleInterface

これは完全に空の最も単純なインターフェースです。 これらはマーカーインターフェイスとも呼ばれます。

次に、インターフェイスにいくつかの関数を追加しましょう。

interface SimpleInterface {
    fun firstMethod(): String
    fun secondMethod(): String {
        return("Hello, World!")
    }
}

以前に定義したインターフェイスに2つのメソッドを追加しました。

  • f irstMethodと呼ばれるそれらの1つは抽象メソッドです
  • 一方、s econdMethodと呼ばれるもう1つにはデフォルトの実装があります。

先に進んで、インターフェイスにいくつかのプロパティを追加しましょう。

interface SimpleInterface {	
    val firstProp: String
    val secondProp: String
        get() = "Second Property"
    fun firstMethod(): String
    fun secondMethod(): String {
        return("Hello, from: " + secondProp)
    }
}

ここでは、インターフェイスに2つのプロパティを追加しました。

  • firstProp と呼ばれるものの1つは、String型であり、抽象的です。
  • secondProp と呼ばれる2番目のものも文字列型ですが、アクセサーの実装を定義します。

インターフェイスのプロパティは状態を維持できないことに注意してください。 したがって、以下はKotlinでの違法な表現です。

interface SimpleInterface {
    val firstProp: String = "First Property" // Illegal declaration
}

2.2. インターフェイスの実装

基本的なインターフェースを定義したので、Kotlinのクラスにそれを実装する方法を見てみましょう。

class SimpleClass: SimpleInterface {
    override val firstProp: String = "First Property"
    override fun firstMethod(): String {
        return("Hello, from: " + firstProp)
    }	
}

SimpleClassSimpleInterfaceの実装として定義する場合、は抽象プロパティと関数の実装のみを提供する必要があることに注意してください。 ただし、以前に定義されたプロパティまたは関数をオーバーライドすることもできます。

ここで、クラスで以前に定義されたすべてのプロパティと関数をオーバーライドしましょう。

class SimpleClass: SimpleInterface {
    override val firstProp: String = "First Property"
    override val secondProp: String
        get() = "Second Property, Overridden!"
    
    override fun firstMethod(): String {
        return("Hello, from: " + firstProp)
    }
    override fun secondMethod(): String {
        return("Hello, from: " + secondProp + firstProp)
    }
}

ここでは、以前にインターフェイスSimpleInterfaceで定義されたプロパティsecondPropと関数secondFunctionをオーバーライドしました。

2.3. 委任によるインターフェースの実装

委任は、オブジェクト指向プログラミングのデザインパターンであり、継承ではなく合成によってコードの再利用性を実現します。 これはJavaなどの多くの言語で実装できますが、Kotlinは委任による実装をネイティブでサポートしています。

基本的なインターフェースとクラスから始める場合:

interface MyInterface {
    fun someMethod(): String
}

class MyClass() : MyInterface {
    override fun someMethod(): String {
        return("Hello, World!")
    }
}

これまでのところ、新しいことは何もありません。 しかし今、委任を通じてMyInterfaceを実装する別のクラスを定義できます。

class MyDerivedClass(myInterface: MyInterface) : MyInterface by myInterface

MyDerivedClass は、インターフェイスMyInterfaceを実際に実装する引数としてデリゲートを想定しています。

デリゲートを介してインターフェイスの関数を呼び出す方法を見てみましょう。

val myClass = MyClass()
MyDerivedClass(myClass).someMethod()

ここでは、 MyClass をインスタンス化し、それを MyDerivedClass、のインターフェイスの関数を呼び出すためのデリゲートとして使用しましたが、実際にはこれらの関数を直接実装することはありませんでした。

3. 多重継承

多重継承は、オブジェクト指向プログラミングパラダイムの重要な概念です。 これにより、クラスは、などのインターフェイスなどの複数の親オブジェクトから特性を継承できます。

これにより、オブジェクトモデリングの柔軟性が高まりますが、独自の複雑さが伴います。 その1つが「菱形継承問題」です。

Java 8には、多重継承を可能にする他の言語と同様に、菱形継承問題に対処するための独自のメカニズムがあります。

Kotlinがインターフェースを介してどのように対処するかを見てみましょう。

3.1. 複数のインターフェースを継承する

まず、2つの単純なインターフェイスを定義します。

interface FirstInterface {
    fun someMethod(): String
    fun anotherMethod(): String {
        return("Hello, from anotherMethod in FirstInterface")
    }
}

interface SecondInterface {
    fun someMethod(): String {
        return("Hello, from someMethod in SecondInterface")
    }
    fun anotherMethod(): String {
        return("Hello, from anotherMethod in SecondInterface")
    }
}

両方のインターフェースに同じコントラクトを持つメソッドがあることに注意してください。

次に、これらの両方のインターフェースから継承するクラスを定義しましょう。

class SomeClass: FirstInterface, SecondInterface {
    override fun someMethod(): String {
        return("Hello, from someMethod in SomeClass")
    }
    override fun anotherMethod(): String {
        return("Hello, from anotherMethod in SomeClass")
    }
}

ご覧のとおり、SomeClassFirstInterfaceSecondInterfaceの両方を実装しています。 構文的にはこれは非常に単純ですが、ここで注意が必要なセマンティクスが少しあります。 これについては、次のサブセクションで説明します。

3.2. 競合の解決

複数のインターフェースを実装する場合、クラスは、複数のインターフェースで同じコントラクトのデフォルトの実装を持つ関数を継承する場合があります。 これにより、実装クラスのインスタンスからこの関数を呼び出すという問題が発生します。

この競合を解決するために、Kotlinは、解決を明示的にするために、そのような関数のオーバーライドされた実装を提供するサブクラスを要求します

たとえば、上記のSomeClassanotherMethodを実装します。 ただし、そうでない場合、KotlinはFirstまたはSecondInterfaceのデフォルトのanotherMethodの実装を呼び出すかどうかを知りません。  SomeClass は、この理由でanotherMethodを実装する必要があります。

ただし、 someMethod は、実際には競合がないため、少し異なります。  FirstInterface は、someMethodのデフォルトの実装を提供していません。 とはいえ、 SomeClass は、親インターフェイスで1回または複数回定義されているかどうかに関係なく、 Kotlinがすべての継承された関数を実装するように強制するため、それを実装する必要があります。

3.3. 菱形継承問題の解決

「菱形継承問題」は、ベースオブジェクトの2つの子オブジェクトが、ベースオブジェクトによって定義された特定の動作を記述している場合に発生します。 ここで、これらの両方の子オブジェクトから継承するオブジェクトは、サブスクライブする継承された動作を解決する必要があります。

この問題に対するKotlinの解決策は、前のサブセクションで多重継承に対して定義されたルールを使用することです。 菱形継承問題を提示するために、いくつかのインターフェースと実装クラスを定義しましょう。

interface BaseInterface {
    fun someMethod(): String
}

interface FirstChildInterface: BaseInterface {	
    override fun someMethod(): String {
        return("Hello, from someMethod in FirstChildInterface")
    }
}

interface SecondChildInterface: BaseInterface {
    override fun someMethod(): String {
        return("Hello, from someMethod in SecondChildInterface")
    }
}

class ChildClass: FirstChildInterface, SecondChildInterface {
    override fun someMethod(): String {
        return super<SecondChildInterface>.someMethod()
    }
}

ここでは、someMethodという抽象関数を宣言するBaseInterfaceを定義しました。 インターフェイスFirstChildInterfaceSecondChildInterfaceはどちらもBaseInterfaceを継承し、関数someMethodを実装します。

FirstChildInterfaceSecondChildInterfaceを継承するChildClassを実装するため、関数someMethodをオーバーライドする必要があります。 ただし、メソッドをオーバーライドする必要がある場合でも、 ここでSecondChildInterfaceを使用して行うように、単にsuperを呼び出すことができます。

4. Kotlinの抽象クラスと比較したインターフェース

Kotlinの抽象クラスは、インスタンス化できないクラスです。 これには、1つ以上のプロパティと関数が含まれる場合があります。 これらのプロパティと関数は、抽象的または具体的です。 抽象クラスから継承するクラスは、そのクラス自体も抽象として宣言されていない限り、継承されたすべての抽象プロパティと関数を実装する必要があります。

4.1. インターフェイスと抽象クラスの違い

待って! それはインターフェースが行うこととまったく同じように聞こえませんか?

実際、最初は、抽象クラスはインターフェースとそれほど変わりません。 しかし、私たちが行う選択を左右する微妙な違いがあります。

  • Kotlinのクラスは、必要な数のインターフェイスを実装できますが、1つの抽象クラスからのみ拡張できます。
  • インターフェイスのプロパティは状態を維持できませんが、抽象クラスのプロパティは維持できます

4.2. いつ何を使うべきですか?

インターフェイスは、クラスを定義するための単なる青写真であり、オプションでいくつかのデフォルトの実装を持つこともできます。 一方、抽象クラスは、拡張クラスによって完成される不完全な実装です。

通常、インターフェイスを使用してコントラクトを定義する必要があります。これにより、提供が約束されている機能が引き出されます。 実装クラスは、これらの約束を果たす責任があります。 ただし、抽象クラスは、拡張クラスと部分的な特性を共有するために使用する必要があります。 拡張クラスは、それを完了するためにさらに進むことができます。

5. Javaインターフェイスとの比較

Java 8でのJavaインターフェースの変更により、それらはKotlinインターフェースに非常に近づきました。 以前の記事の1つは、インターフェースへの変更を含むJava8で導入された新機能をキャプチャしています。

現在、JavaインターフェースとKotlinインターフェースの間には構文上の違いがほとんどあります。 目立つ違いの1つは、キーワード「オーバーライド」に関連しています。 Kotlinでは、インターフェイスから継承された抽象プロパティまたは関数を実装する際に、キーワード「override」でそれらを修飾する必要があります。 Javaにはそのような明示的な要件はありません。

6. 結論

このチュートリアルでは、Kotlinインターフェース、それらを定義および実装する方法について説明しました。 次に、複数のインターフェイスからの継承と、それらが作成する可能性のある競合について説明しました。 Kotlinがこのような競合をどのように処理するかを調べました。

最後に、Kotlinの抽象クラスと比較したインターフェースについて説明しました。 また、KotlinインターフェイスとJavaインターフェイスの比較についても簡単に説明しました。

いつものように、例のコードはGitHubから入手できます。