はじめに
前回、PHPerだけどKotlinを勉強したって良いよね その1〜クラス編〜の続きになります。
今回はKotlinにおけるクラスのコンストラクタになります。
勉強に使った本はKotlinイン・アクション、環境はkotlincを使用しています。
コンストラクタ
Kotlinにはプライマリコンストラクタとセカンダリコンストラクタがあります。
プライマリコンストラクタ
1 | class User(val nickname: String) |
これは以下の宣言と同じです。
1 2 3 4 5 6 7 8 | class User constructor(_nickname: String) { val nickname: String // 初期化ブロック init { nickname = _nickname } } |
- プロパティに引数をセットするだけであれば、
init
ブロックも必要ない - 上記の例であれば、
constructor
キーワードも省略できる。 - ただ、
val
を引数につける。 - デフォルト引数も持てる。
1 2 | class User(val nickname: String, val isSubscribed: Boolean = true) |
インスタンス化する場合は
new
も不要です。
1 2 3 4 5 6 7 8 9 10 | >> 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 |
クラスがスーパークラスを持つ場合は、スーパークラスも初期化する必要があります。
1 2 | open class User(val nickname: String) { ... } class TwitterUser(nickname: String) : User(nickname) { ... } |
コンストラクタを明示的に指定しない場合は、コンパイル時にデフォルトコンストラクタが生成される。
1 | open class Button |
ただし、デフォルトコンストラクタが生成されているため、このクラスを継承する場合は明示的にコンストラクタを呼び出す必要があります。
これが、継承する際にスーパークラスのクラス名の後にカッコが必要な理由のようです。
1 | class RadioButton : Button() |
インターフェースはコンストラクタを持たないため、カッコをつける必要はありません。
セカンダリコンストラクタ
Kotlinでは複数のコンストラクタを持つことは少ないようです。
大体がプライマリコンストラクタで解決されるからです。
ただ、例えばJavaで宣言された2つのコンストラクタを持つViewクラスを考えてみます。
Kotlinだとこのように書き換えられます。
1 2 3 4 5 6 7 8 | open class View { constructor(ctx: Context) { // some code } constructor(ctx: Context, attr: AttributeSet) { // some code } } |
このクラスを拡張する場合、同じコンストラクタを宣言できます。
1 2 3 4 5 6 7 8 | class MyButton : View { constructor(ctx: Context) : super(ctx) { // ... } constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) { // ... } } |
this()
キーワードを使ってあるコンストラクタから自分のクラスの別のコンストラクタを呼び出すこともできます。
1 2 3 4 5 6 7 8 | class MyButton : View { constructor(ctx: Context) : this(ctx, MY_STYLE) { // ... } constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) { // ... } } |
インターフェース内で宣言されたプロパティ
1 2 3 | interface User { val nickname: String } |
Userインターフェースを実装するクラスが、
nickname
の値を取得する手段を用意する必要があります。
実際に実装してみる。
ニックネームのみを入力した人
1 | class PrivateUser(override val nickname: String) : User |
1 2 | >> println(PrivateUser("test@kotlinlang.org").nickname) test@kotlinlang.org |
ニックネームと email を入力した人
1 2 3 4 5 | class SubscribingUser(val email: String) : User { override val nickname: String // カスタムgetter get() = email.substringBefore('@') } |
1 2 | >> println(SubscribingUser("test@kotlinlang.org").nickname) test |
Facebookのアカウントで登録した人
多分どこかで定義されているであろう
getFacebookName
メソッドを返します。
1 2 3 | class FacebookUser(val accountId: Int) : User { override val nickname = getFacebookName(accountId) } |
getter、setterからバッキングフィールドにアクセスする
バッキングフィールドを参照しない場合、getter、setterを持ったプロパティをインターフェースに含めることができます。
1 2 3 4 5 | interface User { val email: String val nickname: String get() = email.substringBefore('@') } |
この場合は
email
はサブクラスによってオーバーライドされる必要がありますが、
nickname
はそのまま使えます。
バッキングフィールドを参照してしまうと、インターフェースが状態を持ってしまうのでできません。
Userクラスで、プロパティに格納されているデータが変更された場合、ログに残すようsetterを定義します。
1 2 3 4 5 6 7 8 9 10 | class User(val name: String) { var address: String = "unspecified" set(value: String) { println(""" Address was changed for $name: "$field" -> "$value".""".trimIndent()) // バッキングフィールドの値を更新する field = value // バッキングフィールドの値を更新する } } |
1 2 3 4 5 6 7 8 9 10 11 | >>> 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のどちらか一つを再定義することもできます。
バッキングフィールドへの明示的な参照とデフォルトアクセサを使用する場合、コンパイラはプロパティのバッキングフィールドを生成するようです。
アクセサの可視性
アクセサの可視性はデフォルトでプロパティと同じです。
ただし、必要に応じて可視性を変更することができます。
1 2 3 4 5 6 7 8 9 | class LengthCounter { var counter: Int = 0 // このプロパティはクラスの外側からは変更できない private set fun addWord(word: String) { counter += word.length } } |
1 2 3 4 | >>> val LengthCounter = LengthCounter() >>> LengthCounter.addWord("Hi!") >>> println(LengthCounter.counter) 3 |
さいごに
勉強のまとめとして記載させていただきました。
次回はクラス委譲などをまとめたいと思っています。