Edit Page

クラスと継承

クラス

Kotlinでのクラスは、classキーワードを使用して宣言されます。

class Invoice {
}

クラス宣言はクラス名、クラスヘッダ(その型パラメータ、主コンストラクタ等)、そして波括弧で括られたクラス本体で構成されます。ヘッダと本体は両方とも必須ではありません。クラスに本体がない場合は、波括弧を省略することができます。

class Empty

コンストラクタ

Kotlin内のクラスは、 プライマリコンストラクタ と1つまたは複数の セカンダリコンストラクタ を持つことができます。プライマリコンストラクタは、クラスのヘッダーの一部です。クラス名(型パラメータをつけることもできます)の後に続きます。

class Person constructor(firstName: String) {
}

プライマリコンストラクタがアノテーションや可視性修飾子を持っていない場合は、 constructorのキーワードを省略することができます。

class Person(firstName: String) {
}

プライマリコンストラクタは、どんなコードも含めることはできません。初期化コードは、initキーワードが付いている 初期化ブロック内 に書くことができます。

class Customer(name: String) {
    init {
        logger.info("Customer initialized with value ${name}")
    }
}

プライマリコンストラクタの引数を初期化ブロックに使用できることに注意してください。クラス本体内で宣言されたプロパティの初期化処理で使用することもできます。

class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

実際には、プロパティの宣言と初期化を主コンストラクタから行うために、Kotlinは簡潔な構文があります:

class Person(val firstName: String, val lastName: String, var age: Int) {
  // ...
}

通常のプロパティとほとんど同じ方法のように、プロパティは主コンストラクタの中で可変値(ミュータブル) ( var ) または固定値(イミュータブル) ( val ) で宣言することができます。

もしコンストラクタがアノテーションや可視性修飾子を持つ場合は、 constructor キーワードが必要で修飾子はその前に置かれる:

class Customer public @Inject constructor(name: String) { ... }

詳細については、可視性修飾子を参照してください。

セカンダリコンストラクタ

クラスは、 constructor プレフィクスと共に セカンダリコンストラクタ を宣言することができます:

class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

もしクラスがプライマリコンストラクタを持つなら、それぞれのセカンダリコンストラクタは直接的または間接的に、他のセカンダリコンストラクタを介してプライマリコンストラクタへ委譲する必要があります。 同クラスの他コンストラクタへの委譲は this キーワードを用いて行います:

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

もし非抽象クラスが何もコンストラクタ(プライマリ、セカンダリ共に)を宣言しなければ、プライマリコンストラクタが引数無しで生成されます。その際のコンストラクタの可視性はpublicになります。もしpublicなコンストラクタを望まないならば、空の主コンストラクタをデフォルトでない可視性で宣言する必要があります。

class DontCreateMe private constructor () {
}

注意: JVMでは、プライマリコンストラクタの全ての引数がデフォルト値を持つなら、 コンパイラは引数無しコンストラクタを追加で生成し、そのコンストラクタはデフォルト値を使用します。 これにより、JacksonやJPAのように引数が無いコンストラクタを通してクラスインスタンスを作るようなライブラリを、 Kotlinで使いやすくなります。

class Customer(val customerName: String = "")

クラスのインスタンス生成

クラスのインスタンスを生成するには、コンストラクタを普通の関数のように呼び出せば良いです:

val invoice = Invoice()

val customer = Customer("Joe Smith")

Kotlinは new キーワードを持たないことに注意してください。

ネストされたクラス、インナークラス、そして匿名のインナークラスの生成はネストされたクラスの中に記述されています。

クラスメンバ

クラスは以下を含めることができます:

継承

Kotlinの全てのクラスは共通の Any スーパークラスをもちます。これはスーパータイプの宣言がないクラスのデフォルトのスーパークラスです。

class Example // Anyから暗黙の継承

Anyjava.lang.Object ではありません。特に注意すべきは、 equals()hashCode()toString() 以外のメンバを持ちません。 詳細については Javaとの相互運用性 を参照してください。

クラスヘッダ内のコロンの後に型を書くと、明示的にスーパータイプを宣言できます:

open class Base(p: Int)

class Derived(p: Int) : Base(p)

もしこのような(明示的にスーパータイプを宣言する)クラスがプライマリコンストラクタをもつなら、基底の型をプライマリコンストラクタの引数を使用して、そこで初期化できる(し、しなければいけません)。

もしこのようなクラスがプライマリコンストラクタを持たないならば、セカンダリコンストラクタはそれぞれ基底の型を super キーワードを使って初期化するか、他の初期化してくれるコンストラクタに委譲しなければいけません。この事例では異なるセカンダリコンストラクタが異なる基底の型を持つコンストラクタを呼び出していることに注意すること:

class MyView : View {
    constructor(ctx: Context) : super(ctx) {
    }

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
    }
}

クラスの open アノテーションは、Javaの final と反対です:他のクラスがこのクラスから継承することができます。デフォルトでは、Kotlinのすべてのクラスは Effective Java のアイテム17( 継承またはそれの禁止のためのデザインとドキュメント )に合致する final です。

メンバのオーバーライド

前述の通り、私たちはKotlinに明示的にすることにこだわります。そして、Javaとは異なり、Kotlinはメンバをオーバーライドできるメンバ(私たちは open と呼んでいます)とオーバライド自体に明示的アノテーションを必要とします。

open class Base {
  open fun v() {}
  fun nv() {}
}
class Derived() : Base() {
  override fun v() {}
}

override アノテーションは Derived.v() のために必要です。もしなければ、コンパイラは文句を言うでしょう。もし Base.nv() のように open アノテーションが関数になければ、メソッドをサブクラス内で同じ識別子で宣言することは override の有無にかかわらず文法違反です。ファイナルクラス(例えば、 open アノテーションを持たないクラス)の中では、openメンバは禁止されています。

override としてマークされているメンバは、それ自身がopenです。すなわち、サブクラス内でオーバライドされる可能性があります。もし再オーバライドを禁止したければ、 final キーワードを使ってください:

open class AnotherDerived() : Base() {
  final override fun v() {}
}

プロパティのオーバライドもメソッドのオーバライドと同じように動きます。プライマリコンストラクターでプロパティ宣言の一部として、overrideキーワードを使用できることに注意してください。

open class Foo {
    open val x: Int get { ... }
}

class Bar1(override val x: Int) : Foo() {

}

val プロパティを var プロパティでオーバライドすることもでき、その逆もまた然りです(逆もまた同じです)。これは、val のプロパティは、本質的にgetterメソッドを宣言しているためであり、それを var としてオーバライドすることは、さらにsetterメソッドを派生クラスに宣言しているためです。

待って!じゃあどうやって自分のライブラリをハックすれば良いの?!

オーバライドのKotlinでの方法(クラスやメンバはデフォルトでfinal)には1つ問題があります。あなたが使用しているライブラリ内の何かをサブクラス化し、 いくつかのメソッドをオーバライドして(ライブラリの設計者はそれを意図していない) そこにいくつかの厄介なハックを導入するのが難しくなる、という問題です。

私たちは、次のような理由から、これは欠点ではないと考えています:

  • ベストプラクティスは「とにかくこれらのハックを許可すべきではない」ということである
  • 同様のアプローチを取っている他の言語 (C++, C#) はうまくいっている
  • もし本当にこのハックが必要ならば、それでも方法は残っている:いつでもハックをJavaで書き、Kotlinから呼ぶことができる( Java Interopを参照 してください )し、Aspectフレームワークはいつもこれらの目的にかないます

ルールのオーバーライド

Kotlinでは、継承の実装は次のルールで定められています:もしクラスが直接のスーパークラスから同じメンバの多くの実装を継承する場合、クラスはこのメンバを継承し、その独自の実装(おそらく、継承されたものの一つを使用して)を提供しなければいけません。スーパータイプの名前を角括弧で記述し、 super キーワードを使用すると、継承された実装のスーパータイプであることを示すことができます。 例: super<Base>

open class A {
  open fun f() { print("A") }
  fun a() { print("a") }
}

interface B {
  fun f() { print("B") } // インタフェースのメンバはデフォルトで'open'
  fun b() { print("b") }
}

class C() : A(), B {
  // オーバライドするためにコンパイラは f() を要求する
  override fun f() {
    super<A>.f() // A.f()の呼び出し
    super<B>.f() // B.f()の呼び出し
  }
}

AB の両方から継承するのは問題なく、 C はそれらの関数の唯一の実装であるため a()b() も同様です。しかし f() については、2つの実装が C に継承されているため、 C にある f() をオーバライドし、曖昧さを排除するため独自の実装を提供する必要があります。

抽象クラス

クラスとそのメンバは abstract を使用して抽象クラス・抽象メンバとして宣言することができます。抽象メンバはそのクラス内に実装を持ちません。抽象クラスや抽象関数にopenアノテーションを付ける必要はないことに注意してください。もっとも、それは言うまでもないことですが。

非抽象オープンメンバを抽象メンバでオーバライドすることもできます。

open class Base {
  open fun f() {}
}

abstract class Derived : Base() {
  override abstract fun f()
}

コンパニオンオブジェクト (Companion Objects)

Kotlinでは、JavaやC#とは異なり、クラスはstaticメソッドを持ちません。ほとんどの場合、代替として、パッケージレベルの関数を使用することが推奨されています。

もしクラスインスタンスを持たずに呼べるがクラス内部(例えばファクトリメソッド)へのアクセスが要る関数を書く必要があれば、そのクラスの中で オブジェクト宣言 のメンバとして書くことができます。

特に、もし コンパニオンオブジェクト をクラス内で宣言した場合であっても、クラス名を識別子として、static関数をJava/C# で呼ぶのと同じ構文でそのメンバを呼ぶことができます。

シールクラス (Sealed Classes)

値が制限されたセットの1つの型を持つが、他の型を持てない場合、シールクラスが制限されたクラス階層を表現する際に用いられます。それらはある意味、enum(列挙型)クラスの拡張です。enum型の値のセットも同じく制限されているが、それぞれのenum定数はシングルインスタンスとしてのみ存在し、シールクラスのサブクラスは状態を保持できる複数のインスタンスをもつことができます。

sealed 修飾子をクラス名の前に置くと、シールクラスを宣言できます。ルクラスはサブクラスを持つことができますが、それらは全てシールクラス自身の宣言の中にネストされていなければいけません。

sealed class Expr {
    class Const(val number: Double) : Expr()
    class Sum(val e1: Expr, val e2: Expr) : Expr()
    object NotANumber : Expr()
}

シールクラスのサブクラス(間接的な継承)を拡張するクラスはどこにでも置くことができ、シールクラスの中に書く必要はないことに注意してください。

シールクラスの主な利点は when の中で使用されたときに発揮されます。もし文が全ての事象をカバーすることを確認・証明できれば、 else 句を追加する必要はありません。

fun eval(expr: Expr): Double = when(expr) {
    is Expr.Const -> expr.number
    is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
    Expr.NotANumber -> Double.NaN
    // 全ての事例を嘗めたため、`else` 句は不要
}