はじめに
その3の続きです。クラスについて書きます。
クラス宣言
クラス宣言は下記。
1 2 3 4 5 |
class Hoge { } // 中身がにない場合は波括弧省略可 class Piyo |
コンストラクタ
プライマリコンストラクタ
クラスは1つのプライマリコンストラクタを持つことができる。(Swift でいうイニシャライザ?だと思う)
1 2 3 4 5 |
class Person constructor(firstName: String) { } // 呼び出し val person = Person("hoge") |
プライマリコンストラクタがアノテーションや可視性修飾子を持っていない場合は、 constructor
キーワードを省略することがでる。
1 2 3 4 5 6 |
class Person(firstName: String) { } アノテーションや可視性修飾子ありの場合constructor必須 class Customer public @Inject constructor(name: String) { } |
プライマリコンストラクタにコードを含めることはできないので、初期化コードは init
キーワードの初期化ブロック内に記述する。
1 2 3 4 5 6 |
class Person(firstName: String) { init { // ここで初期化処理 print(firstName) } } |
コンストラクタの引数に val
, var
を付けることでそのままプロパティとして保持することができる。
1 2 3 4 5 |
class Person(val firstName: String, val lastName, var age: Int) { fun printName() { print(firstName + " " + lastName) // プロパティとしてアクセス可 } } |
セカンダリコンストラクタ
constructor
キーワードをつけてセカンダリコンストラクタを宣言できる。セカンダリコンストラクタはプライマリコンストラクタ床となり複数持つことが可能で中にコードを記載できる。
1 2 3 4 5 |
class Person { constructor(firstName: String) { print(firstName) } } |
プライマリコンストラクタを保つ場合はセカンダリコンストラクタでプライマリコンストラクタを呼ぶ必要がある。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Person(val name: String) { constructor(firstName: String, lastName: String) : this(firstName + " " + lastName) } // プライマリコンストラクタ呼び出し val p1 = Person("hoge") // セカンダリコンストラクタ呼び出し val p2 = Person("hoge", "piyo") class Person(val name: String) { init { println("init") } constructor(firstName: String, lastName: String) : this(firstName + " " + lastName) { println("Second") } } val person = Person("hoge", "piyo") // 実行すると init secondの順に出力される |
プロパティ
プロパティにはゲッター、セッターといったアクセサがあり実際の値はバッキングフィールドという変数(field 識別子)に格納されている。(あんまわかってない。。。)
1 2 3 4 5 6 7 8 9 10 11 |
var stringRepresentation: String get() = this.toString() set(value) { // 慣例でセッターの引数はvalueらしい setDataFromString(value) // 文字列をパースして他のプロパティへ値を代入する } var counter = 0 set(value) { if (value >= 0) field = value // セッターでプロパティを書き換える場合はfield(バッキングフィールド)に代入する } |
詳しくはリファレンス
継承
クラスの継承
Kotlin のすべてのクラスは Any
クラスを継承しているらしい。
1 |
class Example // Anyから暗黙の継承 |
継承するクラスには open
修飾子が必要である。(Swift もデフォルト final にしてほしい)
スーパークラスにプライマリコンストラクタがある場合はスーパクラスのコンストラクタを明示する必要がある。
1 2 3 |
open class Base(p: Int) class Derived(p: Int) : Base(p) |
メソッドのオーバーライド
メソッドをオーバーライドする場合も open
修飾子が必要。
1 2 3 4 5 6 7 8 |
open class Base { open fun v() {} // オーバライド可 fun nv() {} // オーバライド不可 } class Derived() : Base() { override fun v() {} } |
プロパティもオーバーライドすることができる。
1 2 3 4 5 6 7 8 |
open class Foo { open val x: Int get { ... } } // プライマリコンストラクタの引数としてもオーバーライド可 class Bar(override val x: Int) : Foo() { } |
override
はそれ自身が open
なのでサブクラスにオーバーライドさせたくない場合は final
修飾子をつけます。
1 2 3 4 |
open class AnotherDerived() : Base() { final override fun nv() {} // オーバライド不可 override fun v() {} // オーバライド可 } |
インターフェイス
インターフェイスはたぶん Swift でいうプロトコルだと思う。
1 2 3 4 5 6 7 8 9 |
interface Animal { fun cry() // デフォがopenになる } class Cat: Animal { override fun cry() { print("にゃ〜") } } |
プロパティを持たせたり、関数にデフォルト実装を行うこともできる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
interface Animal { val name: String // デフォ値を持たせる事はできない( override必須) fun cry() // override必須 fun scream() { // override必須ではない println("きゃ〜") } } class Cat : Animal { override val name = "ねこ" override fun cry() { println("にゃ~") } } val cat = Cat() println(cat.name) // ねこ cat.cry() // にゃ〜 cat.scream() // きゃ〜 |
抽象クラス
メソッドなどの実装をクラスに強制させるにはインターフェイス以外にも抽象クラスを使う方法がある。(たぶんSwiftにはない概念)
抽象クラスには abstract
修飾子を付ける。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
abstract class Animal { abstract val name: String // override必須 val age = 10 // override不可する場合open必要 abstract fun cry() // override必須 fun scream() { // override不可する場合open必要 println("きゃ〜") } } class Cat : Animal() { override val name = "ねこ" override fun cry() { println("にゃ~") } } val cat = Cat() println(cat.name) // ねこ println(cat.age) // 10 cat.cry() // にゃ〜 cat.scream() // きゃ〜 |
データクラス
データ表現用クラス。Swiftでいう構造体のような感じかも?(ちょっと違う気もするど)
class
の前に data
をつけるとデータクラスになる。
1 |
data class User(val name: String, val age: Int) |
データクラスは equals
, hashCode
, toString
, copy
関数を自動で実装してくれる。
比較(equals(), hashCode())
例えば通常のクラスでユーザー型を作った場合、2つのユーザーが同一か比較するには下記のように equals
と hashCode
を実装する必要がある。
hashCode
の実装に関しては下記参考。
Kotlin data classのhashCodeの実装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class User(val name: String, val age: Int) { override fun equals(other: Any?): Boolean { val o = other as? User ?: return false return name == o.name && age == o.age } override fun hashCode(): Int { return name.hashCode() * 31 + age * 31 } } val user1 = User("Tanaka", 29) val user2 = User("Tanaka", 29) println(user1 == user2) // true |
データクラスの場合は必要ない。
1 2 3 4 5 6 7 |
data class User(val name: String, val age: Int) val user1 = User("Tanaka", 29) val user2 = User("Tanaka", 29) // equals, hashCodeが自動で実装されるので比較可能 println(user1 == user2) // true |
ログ出力(toString())
通常のクラスの場合
1 2 3 4 5 6 |
class User(val name: String, val age: Int) val user1 = User("Tanaka", 29) val user2 = User("Tanaka", 29) println(user1) // User@74d57ed ハッシュ値が返る println(user2) // User@74d57ed ハッシュ値が返る |
データクラスの場合
1 2 3 4 5 6 7 |
data class User(val name: String, val age: Int) val user1 = User("Tanaka", 29) val user2 = User("Tanaka", 29) // toStringが自動で実装されるので意味のある値になる println(user1) // User(name=Tanaka, age=29) println(user2) // User(name=Tanaka, age=29) |
コピー(copy())
データのコピーをする際もデータクラスは下記のように copy
関数を自動実装してくれる。
1 |
fun copy(name: String = this.name, age: Int = this.age) = User(name, age) |
copy
関数を使うと次のように一部の値のみを書き換えたデータを簡単に作成できる。
1 2 3 4 |
val user1 = User("Tanaka", 29) val user2 = user1.copy(age = 31) println(user1) // User(name=Tanaka, age=29) println(user2) // User(name=Tanaka, age=31) |
列挙型クラス
基本形
1 2 3 |
enum class Direction { NORTH, SOUTH, WEST, EAST } |
プロパティを持たせる
1 2 3 4 5 6 7 8 9 10 11 12 |
enum class Animal(val voice: String) { DOG("わん"), CAT("にゃ〜"), BIRD("ぴよ"); val isDog: Boolean get() = this == DOG } println(Animal.DOG.voice) // わん println(Animal.DOG.isDog) // true println(Animal.CAT.isDog) // false |
Swiftの下記のようなことはできないみたい
1 2 3 |
enum Animal { case dog(voice: String) } |
関数を持たせる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
enum class Animal { DOG, CAT, BIRD; fun cry() { when (this) { DOG -> println("わん") CAT -> println("にゃ〜") BIRD -> println("ぴよ") } } } Animal.CAT.cry() // にゃ〜 // これと同じ?? enum class Animal { DOG { override fun cry() = println("わん") }, CAT { override fun cry() = println("にゃ〜") }, BIRD { override fun cry() = println("ぴよ") }; abstract fun cry() } |
列挙型クラスには下記の関数とプロパティが実装されている
1 2 3 4 5 |
EnumClass.valueOf(value: String): EnumClass EnumClass.values(): Array<EnumClass> // swiftでいうallCase()?? val name: String val ordinal: Int |
1 2 3 4 5 6 7 8 9 10 |
enum class Animal { DOG, CAT, BIRD; } println(Animal.BIRD.name) // BIRD println(Animal.BIRD.ordinal) // 2 println(Animal.values()[0]) // DOG println(Animal.valueOf("CAT")) // CAT println(Animal.valueOf("PIG")) // java.lang.IllegalArgumentException |
オブジェクト
object
キーワードというものがあり、以下3つの場面で利用するらしい。
オブジェクト宣言
シングルトンオブジェクトを作成するときに使う。
1 2 3 4 5 6 7 |
object Hoge { var foo = "FOO" fun piyo() = println("ぴよ") } println(Hoge.foo) // FOO Hoge.piyo() // ぴよ |
コンパニオンオブジェクト
クラスにstaticメソッドやプロパティを持たせたいときに使う。
1 2 3 4 5 6 7 8 9 |
class Hoge { companion object { var foo = "FOO" fun piyo() = println("ぴよ") } } println(Hoge.foo) // FOO Hoge.piyo() // ぴよ |
これを使うと private
関数にアクセスできるので下記のようなことができるらしい。
1 2 3 4 5 6 7 8 9 10 |
class User private constructor(private val name: String) { companion object { fun newUser(name: String) = User(name) } fun printName() = println(name) } val user = User.newUser("Tanaka") user.printName() // Tanaka |
オブジェクト式
無名クラスを作成できる?これはちょっとよくわかってない。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
fun countClicks(window: JComponent) { var clickCount = 0 var enterCount = 0 // MouseAdapterを継承した無名クラスのオブジェクトを作ってaddMouseListenerに渡す window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { clickCount++ // この無名クラスの外側にあるclickCountにアクセスできる。 // しかもvalじゃなくてvarであっても。 } override fun mouseEntered(e: MouseEvent) { enterCount++ } }) // ... } // 何も継承を指定しない(Anyのみ継承する)オブジェクトも作れる val adHoc = object { var x: Int = 0 var y: Int = 0 } print(adHoc.x + adHoc.y) |
さいごに
これで Kotlin については一通り完成!と思ったけどあとは軽くコーディング規約について書こうと思います。
参考
- Kotlinリファレンス Properties and Fields
- Kotlinリファレンス Classes and Inheritance
- Kotlinリファレンス クラスと継承
- Kotlin data classのhashCodeの実装
- 30分で覚えるKotlin文法
コメント
[…] その4の続きです。コーディング規約について書きます。 基本的には下記参考 Kotlin スタイルガイド […]