Kotlin使用(二)クラスとオブジェクト

19733 ワード

クラスと継承
クラス#クラス#
Kotlinでキーワードclassを使用してクラスを宣言
class Invoice {
}

クラス宣言は、クラス名、クラスヘッダ(そのタイプパラメータ、主構造関数などを指定する)、カッコで囲まれたクラスから構成されます.クラスヘッダとクラスボディはオプションです.クラスにクラスがない場合は、カッコを省略できます.
class Empty

コンストラクタ
Kotlinのクラスには、1つのマスターコンストラクション関数と1つ以上のセカンダリコンストラクション関数があります.プライマリコンストラクション関数は、クラス名(およびオプションのタイプパラメータ)の後に続くクラスヘッダの一部です.
class Person constructor(firstName: String) {
}

プライマリコンストラクション関数に注釈または可視性修飾子がない場合は、このconstructorキーワードを省略できます.
class Person(firstName: String) {
}

プライマリコンストラクション関数には、コードを含めることはできません.初期化コードは、initキーワードを接頭辞とする初期化ブロック(initializer blocks)に入れることができる.
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です.クラスに公有構造関数があることを望んでいない場合は、デフォルトの可視性を持たない空のマスター構造関数を宣言する必要があります.
class DontCreateMe private constructor () {
}

注:JVMでは、マスターコンストラクタのすべてのパラメータにデフォルト値がある場合、コンパイラはデフォルト値を使用する追加の非パラメトリックコンストラクタを生成します.これにより、Kotlinは、JacksonやJPAのような無パラメトリック関数によってクラスのインスタンスを作成するライブラリをより容易に使用することができる.
class Customer(val customerName: String = "")

クラスのインスタンスの作成
クラスのインスタンスを作成するには、通常の関数のようにコンストラクション関数を呼び出します.
val invoice = Invoice()

val customer = Customer("Joe Smith")

注意Kotlinにはnewキーワードはありません.
ネストされたクラス
クラスは他のクラスにネストできます
class Outer {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
    }
}

val demo = Outer.Nested().foo() // == 2

内部クラス
クラスはinnerとしてマークされ、外部クラスのメンバーにアクセスできます.内部クラスには、外部クラスのオブジェクトへの参照があります.
class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo() // == 1

定義されたthis式を参照して、内部クラスのthisの曖昧化の使用方法を理解します.
匿名の内部クラス
オブジェクト式を使用して匿名の内部クラスインスタンスを作成するには、次の手順に従います.
window.addMouseListener(object: MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ……
    }

    override fun mouseEntered(e: MouseEvent) {
        // ……
    }
})

オブジェクトが関数Javaインタフェース(単一の抽象メソッドを持つJavaインタフェース)のインスタンスである場合、インタフェースタイプの接頭辞付きlambda式を使用して作成できます.
val listener = ActionListener { println("clicked") }

クラスメンバー
クラスには
  • 構造関数および初期化ブロック
  • 関数
  • 属性
  • ネストクラスと内部クラス
  • オブジェクト宣言
  • 継承
  • Kotlinにはすべてのクラスに共通のスーパークラスAnyがあります.これは、スーパータイプ宣言のないクラスに対してデフォルトのスーパークラスです.
    class Example //   Any     

    Anyはjavaじゃない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内のすべてのクラスがfinalです.
    上書き方法
    私たちは前に述べたように、Kotlinは明確な表現を求めています.Javaとは異なり、Kotlinは上書き可能なメンバー(オープンと呼ぶ)と上書き後のメンバーを明示的にマークする必要があります.
    open class Base {
        open fun v() {}
        fun nv() {}
    }
    class Derived() : Base() {
        override fun v() {}
    }

    Derived.v()関数にoverride寸法を付ける必要があります.書かないと、コンパイラがエラーを報告します.関数にBaseのようなopenがマークされていない場合.nv()の場合、overrideを追加するかどうかにかかわらず、サブクラスで同じ署名を定義する関数は許可されません.1つのfinalクラス(openで表記されていないクラス)では、オープンメンバーは禁止されています.
    overrideとマークされたメンバー自体はオープンです.つまり、サブクラスで上書きできます.再上書きを禁止したい場合はfinalキーワードを使用します.
    open class AnotherDerived() : Base() {
        final override fun v() {}
    }

    属性の上書き
    プロパティのオーバーライドはメソッドのオーバーライドと似ています.スーパークラスで宣言し、派生クラスで再宣言するプロパティはoverrideで始まる必要があり、互換性のあるタイプが必要です.各宣言のプロパティは、初期化器を持つプロパティまたはgetterメソッドを持つプロパティで上書きできます.
    open class Foo {
        open val x: Int get { …… }
    }
    
    class Bar1 : Foo() {
        override val x: Int = ……
    }

    1つのvarプロパティでvalプロパティを上書きすることもできますが、逆にはできません.これは、valプロパティが本質的にgetterメソッドを宣言し、varに上書きするのはサブクラスでsetterメソッドを追加的に宣言するだけであるため許容されます.
    プロパティ宣言の一部としてoverrideキーワードをプライマリコンストラクション関数で使用できることに注意してください.
    interface Foo {
        val count: Int
    }
    
    class Bar1(override val count: Int) : Foo
    
    class Bar2 : Foo {
        override var count: Int = 0
    }

    上書き規則
    Kotlinでは、1つのクラスがその直接スーパークラスから同じメンバーの複数のインプリメンテーションを継承する場合、そのメンバーを上書きし、独自のインプリメンテーションを提供する必要がある(継承の1つを使用する可能性がある)というルールによってインプリメンテーション継承が規定されている.どのスーパータイプから継承された実装を採用するかを示すために、スーパータイプ名によって定義されたsuper(superなど)を使用します.
    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.f()/A.f()を び す
    super.f()/ び しB.f()
    }
    }

    AとBを同時に継承しても問題なく、a()とb()も問題ありません.Cは各関数の1つの実装のみを継承しているからです.しかし、f()はCによって2つのインプリメンテーションを継承するので、私たちはCでf()をカバーし、曖昧さを解消するために私たち自身のインプリメンテーションを提供しなければならない.
    抽象クラス
    クラスおよびその一部のメンバーはabstractとして宣言できます.抽象メンバーは、このクラスで実装する必要はありません.抽象クラスや関数をopenでマークする必要はありません.
    抽象的でないオープン・メンバーを抽象的なメンバーで上書きできます
    open class Base {
        open fun f() {}
    }
    
    abstract class Derived : Base() {
        override abstract fun f()
    }

    インタフェース
    KotlinのインタフェースはJava 8と類似しており,抽象的なメソッドの宣言も実装も含まれている.抽象クラスとは異なり、インタフェースはステータスを保存できません.プロパティはありますが、抽象的に宣言するか、アクセサ実装を提供する必要があります.
    キーワードインタフェースを使用してインタフェースを定義する
    interface MyInterface {
        fun bar()
        fun foo() {
          //       
        }
    }

    実装インタフェース
    1つのクラスまたはオブジェクトは、1つまたは複数のインタフェースを実装することができる.
    class Child : MyInterface {
        override fun bar() {
            //    
        }
    }

    インタフェースのプロパティ
    インタフェースでプロパティを定義できます.インタフェースで宣言されたプロパティは抽象的か、アクセサの実装を提供します.インタフェースで宣言されたプロパティにバックグラウンドフィールド(backing field)は使用できません.したがって、インタフェースで宣言されたアクセサは参照できません.
    interface MyInterface {
        val prop: Int //    
    
        val propertyWithImplementation: String
            get() = "foo"
    
        fun foo() {
            print(prop)
        }
    }
    
    class Child : MyInterface {
        override val prop: Int = 29
    }

    オーバーライド競合の解決
    複数のインタフェースを実装する場合、同じ方法で複数の実装を継承する問題が発生する可能性があります.たとえば
    interface A {
        fun foo() { print("A") }
        fun bar()
    }
    
    interface B {
        fun foo() { print("B") }
        fun bar() { print("bar") }
    }
    
    class C : A {
        override fun bar() { print("bar") }
    }
    
    class D : A, B {
        override fun foo() {
            super.foo()
    super.foo()
    }
    override fun bar() {
    super.bar()
    }
    }

    上記の例では、インタフェースAおよびBは、メソッドfoo()およびbar()を定義している.どちらもfoo()を実現しているが、Bのみがbar()を実現している(bar()はAに抽象と表記されていない.メソッド体がない場合は抽象とデフォルトであるからである).CはAを実現した具体的なクラスであるため、bar()を書き換えてこの抽象的な方法を実現しなければならない.
    しかし、AとBからDを派生させる場合、複数のインタフェースから継承されたすべての方法を実装し、Dがどのように実装されるべきかを示す必要があります.この規則は、単一の実装(bar()を継承する方法にも、複数の実装(foo()を継承する方法にも適用されます.
    汎用型
    Javaと同様に、Kotlinのクラスにもタイプパラメータがあります.
    class Box<T>(t: T) {
        var value = t
    }

    一般的に、このようなクラスのインスタンスを作成するには、タイプパラメータを指定する必要があります.
    val box: Box<Int> = Box<Int>(1)

    ただし、タイプパラメータが推定できる場合、たとえばコンストラクション関数のパラメータから、または他の方法から、タイプパラメータを省略できます.
    val box = Box(1) // 1      Int,             Box

    汎用型の詳細
    列挙クラス
    列挙クラスの最も基本的な使い方は、タイプセキュリティを実現する列挙です.
    enum class Direction {
        NORTH, SOUTH, WEST, EAST
    }

    各列挙定数はオブジェクトです.列挙定数はカンマで区切られます.
    初期化
    各列挙は列挙クラスのインスタンスであるため、初期化されてもよい.
    enum class Color(val rgb: Int) {
            RED(0xFF0000),
            GREEN(0x00FF00),
            BLUE(0x0000FF)
    }

    匿名クラス
    列挙定数は、自分の匿名クラスを宣言することもできます.
    enum class ProtocolState {
        WAITING {
            override fun signal() = TALKING
        },
    
        TALKING {
            override fun signal() = WAITING
        };
    
        abstract fun signal(): ProtocolState
    }

    および対応するメソッド、およびベースクラスを上書きするメソッドです.列挙クラスがメンバーを定義する場合は、Javaのように、セミコロンを使用してメンバー定義の列挙定数定義を区切ります.
    列挙定数の使用
    Javaのように、Kotlinの列挙クラスには、定義された列挙定数をリストし、名前で列挙定数を取得する合成メソッドもあります.これらのメソッドの署名は次のとおりです(列挙クラスの名前がEnumClassであると仮定します):
    EnumClass.valueOf(value: String): EnumClass
    EnumClass.values(): Array

    指定した名前がクラスで定義された列挙定数と一致しない場合、valueOf()メソッドはIllegalArgumentException例外を放出します.
    Kotlin 1.1以降、enumValues()およびenumValueOf()関数を使用して、列挙クラスの定数に汎用的にアクセスできます.
    enum class RGB { RED, GREEN, BLUE }
    
    inline fun Enum> printAllValues() {
        print(enumValues().joinToString { it.name })
    }
    
    printAllValues() //    RED, GREEN, BLUE

    各列挙定数には、列挙クラス宣言で名前と場所を取得する属性があります.
    val name: String
    val ordinal: Int

    列挙定数はまた、自然順序が列挙クラスで定義された順序であるComparableインタフェースを実現します.