カテゴリー: BackEnd

PHPerだけどKotlinを勉強したって良いよね その2〜コンストラクタ編〜

はじめに

前回、PHPerだけどKotlinを勉強したって良いよね その1〜クラス編〜の続きになります。
今回はKotlinにおけるクラスのコンストラクタになります。

勉強に使った本はKotlinイン・アクション、環境はkotlincを使用しています。

コンストラクタ

Kotlinにはプライマリコンストラクタとセカンダリコンストラクタがあります。

プライマリコンストラクタ

class User(val nickname: String)

これは以下の宣言と同じです。

class User constructor(_nickname: String) {
      val nickname: String

      // 初期化ブロック
      init {
          nickname = _nickname
      }
}
  • プロパティに引数をセットするだけであれば、 `init` ブロックも必要ない
  • 上記の例であれば、 `constructor` キーワードも省略できる。
  • ただ、 `val` を引数につける。
  • デフォルト引数も持てる。
class User(val nickname: String,
                      val isSubscribed: Boolean = true)

インスタンス化する場合は `new` も不要です。

>> class User(val nickname: String,val isSubscribed: Boolean = true)
>>> val alice = User("Alice")
>>> println(alice.isSubscribed)
true
>>> val bob = User("Bob", false)
>>> println(bob.isSubscribed)
false
>>> val carol = User("Carol", isSubscribed = false)
>>> println(carol.isSubscribed)
false

クラスがスーパークラスを持つ場合は、スーパークラスも初期化する必要があります。

open class User(val nickname: String) { ... }
class TwitterUser(nickname: String) : User(nickname) { ... }

コンストラクタを明示的に指定しない場合は、コンパイル時にデフォルトコンストラクタが生成される。

open class Button

ただし、デフォルトコンストラクタが生成されているため、このクラスを継承する場合は明示的にコンストラクタを呼び出す必要があります。
これが、継承する際にスーパークラスのクラス名の後にカッコが必要な理由のようです。

class RadioButton : Button()

インターフェースはコンストラクタを持たないため、カッコをつける必要はありません。

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

Kotlinでは複数のコンストラクタを持つことは少ないようです。
大体がプライマリコンストラクタで解決されるからです。

ただ、例えばJavaで宣言された2つのコンストラクタを持つViewクラスを考えてみます。
Kotlinだとこのように書き換えられます。

open class View {
        constructor(ctx: Context) {
        // some code
        }
        constructor(ctx: Context, attr: AttributeSet) {
        // some code
        }
}

このクラスを拡張する場合、同じコンストラクタを宣言できます。

class MyButton : View {
        constructor(ctx: Context) : super(ctx) {
        // ...
        }
        constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {
        // ...
        }
}

`this()` キーワードを使ってあるコンストラクタから自分のクラスの別のコンストラクタを呼び出すこともできます。

class MyButton : View {
        constructor(ctx: Context) : this(ctx, MY_STYLE) {
        // ...
        }
        constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {
        // ...
        }
}

インターフェース内で宣言されたプロパティ

interface User {
        val nickname: String
}

Userインターフェースを実装するクラスが、 `nickname` の値を取得する手段を用意する必要があります。
実際に実装してみる。

ニックネームのみを入力した人

class PrivateUser(override val nickname: String) : User
>> println(PrivateUser("test@kotlinlang.org").nickname)
test@kotlinlang.org

ニックネームと email を入力した人

class SubscribingUser(val email: String) : User {
        override val nickname: String
        // カスタムgetter
        get() = email.substringBefore('@')
}
>> println(SubscribingUser("test@kotlinlang.org").nickname)
test

Facebookのアカウントで登録した人

多分どこかで定義されているであろう `getFacebookName` メソッドを返します。

class FacebookUser(val accountId: Int) : User {
        override val nickname = getFacebookName(accountId)
}

getter、setterからバッキングフィールドにアクセスする

バッキングフィールドを参照しない場合、getter、setterを持ったプロパティをインターフェースに含めることができます。

interface User {
        val email: String
        val nickname: String
        get() = email.substringBefore('@')
}

この場合は `email` はサブクラスによってオーバーライドされる必要がありますが、 `nickname` はそのまま使えます。
バッキングフィールドを参照してしまうと、インターフェースが状態を持ってしまうのでできません。

Userクラスで、プロパティに格納されているデータが変更された場合、ログに残すようsetterを定義します。

class User(val name: String) {
        var address: String = "unspecified"
        set(value: String) {
                println("""
                        Address was changed for $name:
                        "$field" -> "$value".""".trimIndent())
                 // バッキングフィールドの値を更新する
                 field = value // バッキングフィールドの値を更新する
        }
}
>>> val user = User("Alice")
>>> user.address = "Elesenheimerstrasse 47, 80687 Muenchen"
Address was changed for Alice:
"unspecified" -> "Elesenheimerstrasse 47, 80687 Muenchen".
>>> user.address
Elesenheimerstrasse 47, 80687 Muenchen
>>> user.address = "Shibuya, Tokyo, Japan"
Address was changed for Alice:
"Elesenheimerstrasse 47, 80687 Muenchen" -> "Shibuya, Tokyo, Japan".
>>> user.address
Shibuya, Tokyo, Japan

setter、getterでは `field` という特別な識別子を使えます。
この `field` を呼ぶことで現在バッキングフィールドに格納されている値を参照できます。
getterでは値を読むことしかできないが、setter内では値の変更もできます。
またミュータブルプロパティでは、getterとsetterのどちらか一つを再定義することもできます。
バッキングフィールドへの明示的な参照とデフォルトアクセサを使用する場合、コンパイラはプロパティのバッキングフィールドを生成するようです。

アクセサの可視性

アクセサの可視性はデフォルトでプロパティと同じです。
ただし、必要に応じて可視性を変更することができます。

class LengthCounter {
        var counter: Int = 0
        // このプロパティはクラスの外側からは変更できない
        private set

        fun addWord(word: String) {
                 counter += word.length
        }
}
>>> val LengthCounter = LengthCounter()
>>> LengthCounter.addWord("Hi!")
>>> println(LengthCounter.counter)
3

 

さいごに

勉強のまとめとして記載させていただきました。
次回はクラス委譲などをまとめたいと思っています。

naoki85

シェア
執筆者:
naoki85
タグ: Kotlin

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前