LoginSignup
11
12

More than 5 years have passed since last update.

[Kotlin]インターフェースのデフォルト実装とクラスの委譲の違い

Posted at

実装の共通化ができるインターフェースのデフォルト実装とクラスの委譲でできることが似ているので調べてみました

メソッド

まずはシンプルなものから

インターフェースのデフォルト実装

interface A1{
    fun f(){
        // do something
    }
}

class A1_Class:A1{
    init {
        f()// 呼び出せれる
    }
}

クラスの委譲

interface B1{
    fun f()
}

class C1:B1{
    override fun f() {
        // do something
    }
}

class B1_Class:B1 by C1(){
    init {
        f()//  呼び出せれる
    }
}

違い

(当然のことですが)クラスの委譲で書くことが増える以外違いはなさそうですね

プロパティ

インターフェースにはプロパティも記述できるので

インターフェースのデフォルト実装

interface A2{
    //val p1:String ="" できない
    //var p2:String="" できない
    val p3:String//できた!(ただし同じインスタンスを返せない)
        get() = ""
    var p4:String//できた!(ただし形だけ)
        get() = ""
        set(value) {}
}

class A2_Class:A2{
    init {
        //どちらも参照できる
        print(p3)
        print(p4)
    }
}

値が持てない…

クラスの委譲

interface B2{
    val p1:String
    var p2:String
    val p3:String
    var p4:String
}

class C2:B2{
    override val p1=""
    override var p2=""
    override val p3: String
        get() = ""
    private var _p4 =""
    override var p4: String
        get() = _p4
        set(value) { _p4=value }
}

class B2_Class:B2 by C2(){
    init {
        //もちろん参照できる
        print(p1)
        print(p2)
        print(p3)
        print(p4)
    }
}

違い

インターフェースのデフォルト実装ではプロパティの初期化子が使えないところと値を保持できないところに違いが出てきましたね(多重継承の禁止に関する制約ですかね?)

拡張関数

インターフェースのデフォルト実装

interface A3<T>{
    fun String.f1(){
        //do something
    }

    fun T.f2(){
        //do something
    }

    fun <U> U.f3(){
        //do something
    }

    fun <U> A3<U>.f4(){
        //do something
    }

}

class A3_Class:A3<String>{
    init {
        "".f1()
        "".f2()
        1.f3()
        this.f4()
    }
}

クラスの委譲

interface B3<T>{
    fun String.f1()

    fun T.f2()

    fun <U> U.f3()

    fun <U> B3<U>.f4()
}

class C3<T>:B3<T>{
    override fun String.f1() {
        //do something
    }

    override fun T.f2() {
        //do something
    }

    override fun <U> U.f3() {
        //do something
    }

    override fun <U> B3<U>.f4() {
        //do something
    }
}

class B3_Class:B3<String> by C3(){
    init {
        "".f1()
        "".f2()
        1.f3()
        this.f4()
    }
}

違い

これといった違いはなさそうですね

拡張プロパティ

インターフェースのデフォルト実装

interface A4<T>{
    val String.a1:String
        get() = ""
    val T.a2:String
        get() = ""
    val <U> U.a3:String
        get() = ""
    val <U> A4<U>.a4:String
        get() = ""
}

class A4_Class:A4<String>{
    init {
        print("".a1)
        print("".a2)
        print(1.a3)
        print(this.a4)
    }
}

プロパティと同様に値は持てないみたいです

クラスの委譲

interface B4<T>{
    val String.a1:String
    val T.a2:String
    val <U> U.a3:String
    val <U> B4<U>.a4:String
}

class C4<T>:B4<T>{
    //override val String.a1 = "" できない
    private val _a =""
    override val String.a1: String
        get() = _a
    override val T.a2: String
        get() = _a
    override val <U> U.a3: String
        get() = _a
    override val <U> B4<U>.a4: String
        get() = _a
}

class B4_Class:B4<String> by C4(){
    init {
        print("".a1)
        print("".a2)
        print(1.a3)
        print(this.a4)
    }
}

なぜかプロパティの初期化子が使えない…

違い

当然のことながら拡張プロパティはプロパティと同様の違いがありましたね
記事書いてるときに気づきましたが拡張プロパティって初期化子使えないんですね…

部分実装

インターフェースのデフォルト実装

interface A5{
    fun f1()
    fun f2()
}

interface A5_Sub:A5{
    override fun f1() {
        //do something
    }
}

class A5_Class:A5_Sub{
    override fun f2() {
        //do something
    }

    init {
        f1()
        f2()
    }
}

インターフェースは継承ができるのでオーバーライドしてデフォルトの実装を持たせることができるみたいですね

クラスの委譲

interface B5{
    fun f1()
    fun f2()
}

abstract class  C5:B5{
    override fun f2() {
        //do something
    }
}

// class B5_Class:B5 by C5()コンパイルエラー

抽象クラスはインスタンス作成できないのでコンパイルエラーになります

違い

部分実装できる・できないまで差がでましたね

そのほかに

当たり前ですがクラスの委譲の場合コンストラクタが使えます

まとめ

  • インターフェースのデフォルト実装は値を持てない
  • 部分実装するならインターフェースのデフォルト実装
  • コンストラクタを使うならクラスの委譲
  • 拡張プロパティは初期化子が使えない

考察

インターフェースの定義の一部に実装を持たせたいのならデフォルト実装でその他の場合はクラスの委譲が良いっていう感じですかね?
もちろん混ぜ合わせて使うこともできますし、実装の共通化なんてするのもそれほど機会があるわけでもないので悩むことも少ないですかね

さいごに

多重継承の問題が浮上してくると思いますが自分はそれについてはまだわかってないことがあるのでリンクを張っておきます
ギークを目指して - Java8のインタフェース実装から多重継承とMixinを考える

あと全く関係ない話ですがこれと似たような疑問でC#6.0のreadonlyな変数とgetterのみの(初期化子ありな)自動実装プロパティの違いがわからないんですがわかる人いたら教えてくださいオナシャス

11
12
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
12