前回、PHPerだけどKotlinを勉強したって良いよね その1〜クラス編〜の続きになります。
今回はKotlinにおけるクラスのコンストラクタになります。
勉強に使った本はKotlinイン・アクション、環境はkotlincを使用しています。
Kotlinにはプライマリコンストラクタとセカンダリコンストラクタがあります。
class User(val nickname: String)
これは以下の宣言と同じです。
class User constructor(_nickname: String) { val nickname: String // 初期化ブロック init { nickname = _nickname } }
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
class SubscribingUser(val email: String) : User { override val nickname: String // カスタムgetter get() = email.substringBefore('@') }
>> println(SubscribingUser("test@kotlinlang.org").nickname) test
多分どこかで定義されているであろう `getFacebookName` メソッドを返します。
class FacebookUser(val accountId: Int) : User { override val nickname = getFacebookName(accountId) }
バッキングフィールドを参照しない場合、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
勉強のまとめとして記載させていただきました。
次回はクラス委譲などをまとめたいと思っています。