1. 概要

このチュートリアルでは、継承ルールとKotlinのopenキーワードについて説明します。

まず、継承に関する少しの哲学から始めます。 次に、ギアを切り替えて、 open キーワードがKotlinのクラス、メソッド、およびプロパティにどのように影響するかを確認します。 最後に、openキーワードとSpringなどのエンタープライズフレームワークがどのように相互作用するかを見ていきます。

2. 継承のための設計

Javaでは、クラスとメソッドはデフォルトで拡張可能です。 これは、 final としてマークされていない限り、Javaの任意のクラスから拡張したり、任意のメソッドをオーバーライドしたりできることを意味します。 継承に伴う密接な関係とそのような関係のリスクのために、JoshuaBlochはEffectiveJavaでこれに全項目を捧げました。

継承のための設計と文書化、またはそれを禁止する

これは、クラスとメソッドを拡張またはオーバーライドする正当な理由がない限り、クラスとメソッドを封印し、拡張またはオーバーライドできないようにする必要があることを意味します。 また、クラスを拡張用に開く必要があると判断した場合は、メソッドをオーバーライドした場合の影響を文書化する必要があります。

より良い方法は、デフォルトで最終であることを支持しますが、それでも実装には別のアイデアがあります。

Kotlinが同じ概念にどのようにアプローチするかを見てみましょう。

3. クラス階層

Kotlinでは、すべてがデフォルトで最終版です。 したがって、デフォルト構成の任意のクラスから拡張しようとすると、次のようになります。

class Try
class Success : Try()

その後、コンパイラは失敗します。

Kotlin: This type is final, so it cannot be inherited from

クラスを拡張用にオープンにするには、そのクラスをopenキーワードでマークする必要があります。

open class Try
class Success : Try()

これで、Tryクラスを拡張できます。 Success クラスを拡張する必要がある場合は、openキーワードでもマークする必要があります。 したがって、開いている キーワードは、クラスに推移的な影響を与えません。

open キーワードでクラスをマークしない場合、Kotlinコンパイラはバイトコードレベルでfinalクラスを発行します。

ささいなクラスを使用してこれを確認しましょう:

class Sealed

それでは、生成されたバイトコードを調べてみましょう。

$ kotlinc Inheritance.kt 
$ javap -c -p -v com.baeldung.inheritance.Sealed 
Compiled from "Inheritance.kt"
public final class com.baeldung.inheritance.Sealed
// truncated

ご覧のとおり、このクラスはJavaのfinalクラスと同じです。

4. メソッドとプロパティのオーバーライド

クラスと同様に、デフォルトでは、メソッドとプロパティは、囲んでいるクラスがopen であっても、Kotlinでfinalになります。 例えば:

open class Try {
    fun isSuccess(): Boolean = false
}
class Success : Try() {
    override fun isSuccess(): Boolean = true
}

最終的なメソッドをオーバーライドできないため、コンパイラはSuccessクラスをコンパイルできません。

Kotlin: 'isSuccess' in 'Try' is final and cannot be overridden

クラスと同様に、コンパイラはここでfinalメソッドを発行します。

$ kotlinc Inheritance.kt
$ javap -c -p -v com.baeldung.inheritance.Try
// truncated
public final boolean isSuccess();
   descriptor: ()Z
   flags: (0x0011) ACC_PUBLIC, ACC_FINAL

サブクラスのメソッドをオーバーライドするには、スーパークラスのopenキーワードでそのメソッドをマークする必要があります。

open class Try {
    open fun isSuccess(): Boolean = false
}
class Success : Try() {
    override fun isSuccess(): Boolean = true
}

プロパティについても同じことが言えます。 オープンキーワードでマークしない限り、これらは最終です。

open class Try {
    open val value: Any? = null
    // omitted
}
class Success(override val value: Any?) : Try() {
    // omitted
}

上に示したように、サブクラスコンストラクターのプロパティをオーバーライドしています。

overrideキーワードでマークされたメソッドとプロパティは暗黙的に開かれているため、サブクラスでオーバーライドできます。 これを防ぐために、finalでマークを付けることができます。

open class Success(override val value: Any?) : Try() {
    final override fun isSuccess(): Boolean = true
}

上に示したように、クラス Success のサブクラスは、 isSuccess()メソッドをオーバーライドできません。

5. フレームワークの相互運用性

SpringやHibernateなどのJVMエコシステムの多くのエンタープライズフレームワークは、動的プロキシを作成するために継承に依存しています。 したがって、 finalKotlinクラスをSpringBeanまたはJPAエンティティとして使用することはできません。

この問題を解決して、すべてのSpringBeanまたはJPAエンティティをopenキーワードでマークする1つの方法。 推奨されるメソッドとプロパティにもマークを付ける必要がある場合があります。

@Service
@Transactional
open class UserService {

    open fun register(u: User) {
        // omitted
    }
}

すべての特別なクラスとそのメンバーにopenキーワードでマークを付けることは、不便で効果のないアプローチです。 この状況を改善するために、JetBrainsはKotlin-allopenプラグインを提供しています。 このプラグインは、KotlinクラスをSpringなどのフレームワークの要件に適合させます。

基本的に、このプラグインは、明示的なopen なしで、特定のクラスとそのメンバーをopenキーワードでマークします。 これを実現するには、これらのクラスに@Service@Entityなどの特別なSpringアノテーションを付ける必要があります。

@Service
@Transactional
class UserService {

    fun register(u: User) {
        // omitted
    }
}

これらのopenキーワードを明示的に追加しなかったとしても、コンパイラプラグインはコンパイル中にそれらを追加します。

6. 結論

このチュートリアルでは、Kotlinで継承がどのように機能するかを確認しました。 また、openキーワードがKotlinのクラス階層にどのように影響するかを学びました。

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