LoginSignup
74
76

More than 1 year has passed since last update.

Kotlinチートシート: クラス&コンストラクター編

Last updated at Posted at 2018-01-26

元の記事 → Swift to Kotlinチートシート

コンストラクターの書き方は色々ある。
いくつか制約があり、適当に書くとコンパイル通らないことが多い。

基本形

class Boo() {}

クラス名の後に書く()が基本となるプライマリコンストラクター。
特に引数がなければ省略して以下のようにも書ける(引数なしコンストラクターが自動生成される?)

class Boo {}

プライマリーコンストラクターは必ず実行されなければならない。
別のコンストラクター(後述のセカンダリーコンストラクター)を書く場合は、その中でthisキーワードを使ってプライマリーコンストラクターを呼ぶ必要がある。
※プライマリーコンストラクターを明示的に記載しない場合は、セカンダリーコンストラクターで呼び出す必要はない

引数をとる

class Foo(bar: String, baz: String) {
    val hoge = bar
    val piyo = baz
}

コンストラクターに引数を設けた形。引数はメンバー変数につっこむことができる。
メンバー変数の定義は省略して以下の形でも書ける。Typescript同様。

class Foo(val hoge: String, val piyo: String) {}

初期化処理をする

class Foo(bar: String, baz: String) {
    val hoge = bar
    val piyo = baz
    init {
        // 初期化処理
    }
}

初期化ブロック(init)によって初期化処理を行うことができる。
初期化ブロックはプライマリーコンストラクタ、セカンダリーコンストラクタどちらを実行した場合でも実行される。
セカンダリーコンストラクタを実行した場合は、初期化ブロックが実行された後にセカンダリーコンストラクタの処理が実行される。

class Foo {
    init { }
    init { }
    init { }
}

ちなみに初期化ブロックは複数書けるが、あまり使い所はなさそう。

初期化ブロック(init)ではプロパティが初期化されていない場合がある

class Foo {
    init { println(flag) }
    val flag = true
    init { println(flag) }
}

上記を実行すると、以下の出力になる。

I/System.out: false
I/System.out: true

flagには初期値としてtrueをセットしているが、一つ目のinitではまだ初期化が行われておらずfalseになっている。
init のタイミングでは init より上に書かれたプロパティしか初期化されていないため、initはプロパティ宣言の下に書くのが無難。

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

class Hoge(foo: String) {
    constructor(foo: String, bar: String): this(foo) {}
}

2つめ以降のコンストラクターは constructor キーワードで書き、セカンダリコンストラクターという。
セカンダリコンストラクターでは必ずプライマリーコンストラクターを呼ぶ必要があり、this キーワードを使って呼び出す。(上記の例では、: this(foo) を書かないとエラーになる)

class Baz {
    constructor()
}

上記のようにセカンダリのみ書くこともできるが、あまり意味はない。
ちなみにセカンダリコンストラクターは何も処理がなければ {} を省略できる。

継承一番シンプル

ここから継承するパターン。

open class Bar {}
class Foo: Bar() {}

親クラスにopen修飾子をつけないと継承できない。
親のコンストラクターを呼び出す必要があるので、BarではなくBar()にする必要がある。

親のコンストラクターを呼ぶ

親のコンストラクターはクラス名の定義の後に()で書く方法の他、superで呼び出す方法がある。

open class Base {}
class Foo: Base {
    constructor(): super()
}

ただし、上記のように親のコンストラクターに引数がない場合、superを省略して書ける。

open class Base {}
class Foo: Base {
    constructor()
}

AndroidのViewを継承する場合

class CustomView: View {
    init {
       // 全コンストラクターで共通の初期化処理
    }
    constructor(context: Context): super(context)
    constructor(context: Context, attrs: AttributeSet): super(context, attrs)
    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): super(context, attrs, defStyleAttr)
}

initとセカンダリーコンストラクター実行順序

initとセカンダリーconstructorの実行順序は、initが先でconstructorが後になる。

class Foo {
    init {
        // 先に実行される
    }
    constructor() {
        // 後に実行される
    }
}

継承がある場合、親クラスのinitconstructorが実行されてから、子クラスのinitconstructorが実行される。

class Sub: Base {
    init { /* 3 */ }
    constructor() { /* 4 */ }
}

open class Base {
    init { /* 1 */ }
    constructor() { /* 2 */ }
}

また、プライマリーコンストラクターは init よりさらに先に実行され、initブロック内でプライマリーコンストラクターに引き渡された値を使うことができる。

class Foo(i: Int) {
    init { 
        println(i)
    }
}

privateなコンストラクター

class Foo private constructor () {}

クラス名の後に private constructor が続くのが今までの書き方と比べて特殊に感じそうだが、public なコンストラクターは () の前にある constructorが省略されている形であることを知るとあまり違和感がない。

class Foo() {}

これは下記の省略形である。

class Foo constructor() {}

親クラスの初期化時に呼び出すメソッドを、子クラスでoverrideしてはいけない

クラスのコンストラクターや初期化ブロック(init)が実行されているタイミングでは、子クラスのプロパティーはまだ初期化せれておらず、nullや0やfalseが入った状態になっている。

以下の例では親クラスのinitでviewDidLoadを呼び、子クラスでoverrideしているが、viewDidLoadが実行されるタイミングではまだtextプロパティが初期化されていない。

open class UIViewController {
    init { viewDidLoad() }
    open fun viewDidLoad() {}
}

class FooViewController: UIViewController() {
    val text: String = "Hello World"

    override fun viewDidLoad() {
        super.viewDidLoad()
        println(text) // textはまだ値が入っておらず、nullになっている
    }
}
74
76
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
74
76