Edited at

Java開発者に送るKotlinのClass part.2


可視性

JavaとKotlinの可視性はやや異なります。


  • Kotlinのデフォルト可視性はpublic

  • Kotlinにpackage privateは無い

  • Kotlin独自の可視性internalがある (同一モジュール内で参照可)

  • Kotlinはデフォルトで継承不可 (Javaのfinal扱い)


継承

KotlinでAnimalクラスを継承したDogクラスを定義してみます。

open class Animal { // 継承可なクラスにするためopenをつける

open fun greet() { // オーバーライド可能な関数にするためopenをつける
}
}

class Dog: Animal() { // コロンに続けて親クラス名を
override fun greet() { // override修飾子をつける
println("Bow wow")
}
}

クラスを継承するときは、

class サブクラス名: スーパークラスのコンストラクタ と記述します。

コンストラクタに引数が必要な場合を見てみましょう。

open class Animal(val name: String) {

open fun greet() {
}
}

// Dogのコンストラクタ引数 `name` をAnimalクラスのコンストラクタに渡す
class Dog(name: String): Animal(name) {
override fun greet() {
println("Bow wow")
}
}


コンパニオンオブジェクト

Kotlinにはstatic修飾子はありません。

Kotlinのクラスでstaticメンバやstatic関数を扱いたい場合は、

コンパニオンオブジェクトを使います。

コンパニオンオブジェクトとは、

特別なシングルトンインスタンスです。

class Dog {

companion object {
val ancestorName: String = "wolf"
}

fun greet() {
println("Bow wow")
}
}

val ancestor = Dog.ancestorName

companion objectの中で ancestorName というプロパティを定義しました。

このプロパティにアクセスするには Dog.ancestorName と、staticメンバのようにアクセスします。

※ただし、Javaとの互換性のため、staticとしてコンパイルするためのアノテーションは存在します。


object宣言

Kotlinにはシングルトンを簡単に実装する仕組みがあります。

object ResourceManager {

fun clean() {
// ...
}
}

ResourceManager.clean()

objectキーワードに続けてシングルトンなクラスを定義できます。

通常のクラスと同様にプロパティや関数を定義できますが、

object宣言ではコンストラクタだけ定義できません。

シングルトンなインスタンスにアクセスするにはクラス名を用います。

Javaのstaticメンバへのアクセスに似ています。


dataクラス

データを保持するためのシンプルなクラスは、頻繁に実装しますよね?

data class Point(val x : Int, 

val y: Int) {
}

dataキーワードに続けてクラスを定義します。

data classはプライマリコンストラクタで宣言されたプロパティを全て、以下の関数で考慮します。


  • equals関数...一致チェックにプロパティの値を考慮

  • hashCode関数...ハッシュ値生成にプロパティの値を考慮

  • toString関数...プロパティの値を出力

また、copy関数が自動生成されます。

これは、任意のプロパティを変更しながらコピーすることができます。

fun moveTo(point: Point, newX: Int): Point {

return point.copy(x = newX)
}

プロパティは全てvalで宣言し、クラスはImmutableとしながら、

値を書き換えたコピーは簡単に生成できます。


sealedクラス

sealedクラスは同一ファイル内でしか継承できないクラスです。

sealed class Animal {

}

class Dog: Animal() {
}

class Cat: Animal() {
}

fun greet(animal: Animal) = when(animal) {

is Dog -> "bow wow"
is Cat -> "meow meow"
// else -> elseケースは不要!
}

上記のgreet関数の中のwhenにご注目ください。

elseケースがありません。

これはsealedクラスの効果です。


innerクラス

Javaではクラスの中にクラスを定義する場合、static修飾子をつけるか否かで意味合いが変わります。Kotlinの記述方法と比較してみます。

※あるクラスの中に定義されたAというクラス

Java
Kotlin

static class A
class A

class A
inner class A

Kotlinのデフォルトは

Javaのstatic付きネストクラスと同義となります。


プロパティとバッキングフィールド

Kotlinのプロパティは1行で宣言できますが、

ゲッター、セッターをそれぞれカスタマイズすることが可能です。

class Dog(name: String) {

var name: String = name
get() { // カスタムゲッター
// ゲッターの中ではfield変数が利用できる
// このゲッターはnameに"ちゃん"をつけて返す
return field + "ちゃん"
}
set(value) { // カスタムセッター
// セッターは引数名を指定する
// このセッターは名前の前から空白を除去してフィールドにセットする
field = value.trimMargin()
}

}


デリゲート

Kotlinのクラスはデフォルトで継承不可です。

継承をせずに機能を拡張していく仕組みがあります。

Movableというインターフェースと、

Movableを実装したMovableImplクラスを定義しました。

interface Movable {

fun walk()
fun run()
fun currentCount(): Int
}

class MovableImpl: Movable {

private var count: Int = 0

override fun walk() {
this.count += 1
}
override fun run() {
this.count += 3
}
override fun currentCount(): Int = this.count
}

このMovableの機能を拡張したDogクラスを定義します。

class Dog(movable: MovableImpl): Movable by movable {

}

byキーワードに続けて、Movableの機能を委譲するインスタンスを指定します。

このDogクラスはMovableインターフェースに関する実装が何もありませんが、

自動的にMovableImplに委譲されます。

val dog = Dog(MovableImpl())

dog.walk()
dog.walk()
dog.run()

println(dog.currentCount()) // 5


プロパティデリゲート

プロパティのゲッター、セッターの振る舞いを委譲することができます。

このようなクラスを定義します。

class Dog {

var name: String by NameFormatter()
}

class Cat {
var name: String by NameFormatter()
}

DogクラスもCatクラスもnameというプロパティを持ちます。

どちらもbyに続いてNameFormatterを指定しています。

下のコードを実行してみます。

val dog = Dog()

dog.name = " ぽち"
println(dog.name) //ぽちちゃん
val cat = Cat()
cat.name = " たま"
println(cat.name) //たまちゃん

いずれも、nameに代入した文字列から空白を除去し、

ゲッターではサフィックスとして"ちゃん"が付加されています。

それでは、NameFormatterの実装を見てみましょう。

class NameFormatter {

private var name: String = ""

operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return this.name + "ちゃん"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
this.name = value.trimStart()
}
}

operatorキーワードに続けて、getValueメソッドとsetValueメソッドを実装します。

このように、プロパティの挙動を外部に委譲することができます。