プロパティ

Properties

プロパティの宣言

varキーワードは、変更可能。valキーワードは、読み取り専用となる。

class Address {
    var name: String = "Holmes, Sherlock"
    var street: String = "Baker"
    var city: String = "London"
    var state: String? = null
    var zip: String = "123456"
}

プロパティを使うには、その名前を通してかんたんに参照する。

fun copyAddress(address: Address): Address {
    val result = Address()
    result.name = address.name
    ....
    return result
}

GetterとSetter

プロパティのフルシンタックスは以下になる。

var プロパティ名[: 型] [= 初期化]
    [get()]
    [set(value)]

get()を使うことで、Computed property(計算プロパティ)を宣言することができる

val isEmpty: Boolean
    get() = this.size == 0

setter

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        field = value
    }

プロパティの型は、getterから推測できる場合は、省略できる。

val isEmpty get() = this.size == 0

アクセス権(visibility)の変更もしくは、それに対するアノテートをする必要があるが、デフォルトの実装に変更は必要としない場合、その本体を宣言することなくアクセッサーを定義することができる。

// setterをprivateに変更する
var setterVisibility: String = "abc"
    private set

// setterにアノテートをつける
var setterWithAnnotation:Any? = null
    @Inject set

Backing fields

Kotlinでは、フィールドは値を保持するためのプロパティの一部としてのみ使用される。 フィールドを直接宣言することはできない。

しかしながら、プロパティは背後のフィールドを必要とすることがある。 Kotlinは自動的にそれを提供する。

この背後のフィールドはfield識別子を使ってアクセッサー内部で参照することができる。

var counter = 0  // 初期化は、背後のフィールドに直接値を代入する
    set(value) {
        if (value % 2 == 0) field = value
        else filed = value + 1
    }

Backing properties

暗黙のbacking fieldを使いたくない場合は、backing propertyを使って同じことができる。

// backing property
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap()
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

コンパイル時定数

const修飾子を使うことによって、コンパイル時の定数であることをマークすることができる。 ただし以下の条件がある。

  • トップレベルもしくは、object宣言のメンバ もしくは、companion object
  • Stringもしくは基本型による初期化
  • getterを持たない
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

// 定数は、アノテーションで使用することができる
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }

遅延初期化プロパティと変数

lateinit修飾子をつけることで、遅延初期化することができる。

public class MyTest {
    // lateinitをつけると初期化コードが不要となる
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }
}

初期化済みかどうかは、.isInitializedを使う

if (foo::bar.isInitialized) {
    println(foo.bar)
}

クラス継承

kotlinlang.org

継承

すべてのKotlinのクラスは、Anyを継承している。

class Example // 暗黙的に Any を継承している

Anyは3つのメソッドを持っている。

  • equals()
  • hashCode()
  • toString()

クラスは、デフォルトは、finalであり、継承することができない。

// デフォルトは final がつけられる
class Parent

// This type is final, so it cannot be inherited from
class Child: Parent()

継承したい場合は、openキーワードを付ける必要がある。

open class Base // 継承することができる

継承は、クラスヘッダー部に、コロンのあとに型を置く。

open class Base(p: Int) 

// Baseを継承
class Derived(p: Int) : Base(p)

派生クラスがプライマリコンストラクタを持つならば、 継承クラスもそのプライマリコンストラクタのパラメーターを使って初期化する。

派生クラスがプライマリコンストラクタを持たない場合は、 セカンダリコンストラクタが、superキーワードを使ってbase型を初期化する必要がある。

class MyView : View {
    constructor(ctx: Context) : super(ctx)

    // セカンダリコンストラクタは異なるbaseのコンストラクタを呼ぶことができる
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

## メソッドのオーバーライド

open class Shape { open fun draw() { /.../ } fun fill() { /.../ } }

class Circle() : Shape() { override fun draw() { /.../ }

// NG fill' hides member of supertype 'Shape' and needs 'override' modifier
// fun fill() { /*...*/ }

}

オーバーライドするメソッドは、`override`修飾子をつける。

親クラスのオーバーライドメソッドを子クラスに禁止する場合は、`final`をつける

open class Rectangle() : Shape() { final override fun draw() { /.../ } }

## プロパティのオーバーライド

メソッドと同様にプロパティのオーバーライドもできる。
この際、型を揃える必要がある。また初期化もしくは、getメソッドを宣言する必要がある。
継承するにあたって、`val`から`var`に変更することができる。
(getメソッドに対して、setを追加的にオーバーライドすることが可能)

open class Shape { // 初期化しなかった場合 // Property must be initialized or be abstract // open val vertexCount: Int

open val vertexCount: Int = 0

}

class Rectangle : Shape() { override val vertexCount = 4 }

プライマリコンストラクタ内で、オーバーライドも可能。

interface Shape { // インターフェースのメンバはデフォルトopen val vertexCount: Int }

class Rectangle(override val vertexCount: Int = 4) : Shape

// varに変更も可能 class Rectangle(override var vertexCount: Int = 4) : Shape

## 派生クラスの初期化の順番

最初にベースクラスのコンストラクタに対する引数の評価処理され、
ベースクラスの初期化が完了後、派生クラスの初期化が実行される

open class Base(val name: String) {

init { println("Initializing a base class") }

open val size: Int = 
    name.length.also { println("Initializing size in the base class: $it") }

}

class Derived( name: String, val lastName: String, ) : Base(name.also { println("Argument for the base class: $it") }) {

init { println("Initializing a derived class") }

override val size: Int =
    (super.size + lastName.length).also { println("Initializing size in the derived class: $it") }

}

val d = Derived("tanaka", "yoshio")

/ 実行結果 Argument for the base class: tanaka Initializing a base class Initializing size in the base class: 6 Initializing a derived class Initializing size in the derived class: 12 /

## superclassメソッドへのアクセス

innerクラスからsuperクラスにアクセスするには、`super@Baseクラス`を使う

class FilledRectangle: Rectangle() { override fun draw() { val filter = Filter() filter.drawAndFill() }

inner class Filler {
    fun fill() { println("Filling") }
    fun drawAndFill() {
        super@FilledRectangle.draw() 
        fill()
    }
}

}


# まとめ感想

コンストラクタ周りカオスすぎ。
プライマリコンストラクタで宣言している変数がイニシャライザでも使えるというのもしっくりこない。

クラス まとめ

kotlinlang.org

Classes

classキーワードを使ってクラスを宣言する。

class Invoice { /*....*/ }
  • class header: 型パラメーター, プライマリーコンストラクタ,など
  • class body

headerもbodyもオプションなので、省略が可能。

class Empty

Constructors

以下のコンストラクタを持つ

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

プライマリコンストラクタは、ヘッダーに宣言する。 またプライマリコンストラクタが存在しない場合は、宣言が不要。

class Person constructor(firstName: String) { /*...*/ }

constructorキーワードは大抵の場合において、省略可能。

class Person(firstName: String) { /*...*/ }

プライマリーコンストラクタにはコードを含めることはできない。 初期化コードは、initキーワードを付けた初期化ブロックに配置する。

初期化ブロックは、コードに配置された順番に実行される。

class InitOrderDemo(name: String) {
    // property initializer
    val firstProperty = "First property: $name".also(::println)

    // class initializer
    init {
        println("First initializer block that prints ${name}")
    }

    // property initializer
    val secoundProperty = "Secound property: ${name.length}".also(::println)

    // class initializer
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

InitOrderDemo("hello")

/* 実行結果
First property: hello
First initializer block that prints hello
Secound property: 5
Second initializer block that prints 5
*/

プリマリーコンストラクタのパラメーターは初期化ブロック内部で使用することができる。 またプロパティ初期化でも使用できる。

class Customer(name: String) {
    // property initializers
    val customerKey = name.uppercase()
}

プライマリコンストラクタ内部でプロパティの宣言と初期化が同時に行うことができる。

class Person(val firstName: String, val lastName: String, var age: Int) 

val tanaka = Person("tanaka", "yasushi", 27)

// valプロパティは変更不可
// tanaka.firstName = "yosida"
// varプロパティは変更可
tanaka.age += 1

トレーリングコンマをクラスのプロパティ宣言につけることができる。

class Person(
    val firstName: String,
    val lastName: String,
    var age: Int, // trailing comma

コンストラクタにアノテーションもしくは可視化修飾子(visibility modifiers)がついている場合は、constructorキーワードが必須。

修飾子は、constructorキーワードの前につける。

class Customer private @Inject constructor(name: String) { /*...*/ }
  • 可視化修飾子: デフォルトはpublic

セカンダリコンストラク

constructorキーワードを使って、複数のセカンダリコンストラクタを宣言することができる。

class Pet {
    // セカンダリコンストラクタ
    constructor(owner: Person) {
        owner.pets.add(this)
    }
}

// NG
// val cat = Pet()

// OK
val tanak = Person()
val cat = Pet(tanaka)

プライマリコンストラクタを持つ場合は、 セカンダリコンストラクタ内部で必ず呼び出す必要がある。 この際、thisキーワードを使用する。

class Person(val name:String) {
    constructor(name: String, age: Int) : this(name + age.toString()) {}
}

// プライマリコンストラクタのみ呼び出される
val yoshida = Person("yoshida")
println(yoshida.name) // yoshida

// セカンダリコンストラクタが呼び出される
val tanaka = Person("tanaka", 27)
println(tanaka.name) // tanaka27

セカンダリコンストラクタは、初期化コードがすべて実行されたあとで実行される。

class Constructors {

    constructor(i: Int) {
        println("Constructor $i")
    }

    init {
        println("Init block")
    }
}

val c = Constructors(5)

/* 実行結果 
Init block
Constructor 5
*/

抽象クラスでないクラスが、プライマリ、セカンダリ両方のコンストラクタを持たない場合、 引数なしのプライマリーコンストラクタが生成される。

もし、publicなコンストラクタを持ちたくない場合は、privateをつけてプライマリーコンストラクタを実装する。

class DontCreateMe private constructor () { /*...*/ }

// Cannot access '<init>': it is private in 'DontCreateMe'
val c = DontCreateMe()

クラスのインスタンス生成

通常の関数と同様にコンストラクタを呼び出すことで生成することができる

val invoice = Invoice()

val customer = Customer("Joe Smith")

Note: Kotlinはnewキーワードが不要。

クラスメンバ

  • コンストラク
  • 初期化ブロック
  • 関数
  • プロパティ
  • ネスト内部クラス
  • Object宣言

継承

クラスは相互に派生させることができ、継承階層を構築することができる。

匿名関数

匿名関数

本体の指定は2種類ある

  • 式の代入
  • ブロックの指定

式の代入

fun(a: Int, b:Int):Int = a + b

この場合は、代入を使うことができない

// Assignments are not expressions, and only expressions are allowed in this context
ints.forEach(fun(v) = sum += v)

ブロックの指定

ブロックを指定する場合は、内部で代入が可能。

var sum = 0
ints.forEach(fun(v) {
    sum += v
}

関連

ラムダ式とクラスによる関数型の実装 - あるマのメモ書き

関数リテラルまとめ

kotlinlang.org

関数リテラル

Kotlinは関数リテラルとして以下が用意されている

特徴

関数リテラルは、式として渡されるが、値として振る舞う事ができる

以下の例を考える

max(strings, { a, b -> a.length < b.length })

関数maxは、高階関数であり、第2引数に関数値を受け取る。 この第二引数は関数としての式であり、関数リテラルと呼ばれている。

これは以下の名前付き関数と同等である。

fun compare(a: String, b: String): Boolean = a.length < b.lenbth

ラムダ式構文

Lambda expression syntax

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
  • ラムダ式は、常に波括弧に囲まれる
    • パラメーター宣言部 (->の前)
    • 本体 (->の後)
  • ラムダの返り値の型は基本Unitであり、最後の式が返り値の型として扱われる

パラメーターを宣言した場合は、型アノテーションを省略できる。

val sum = { x: Int, y: Int -> x + y }

トレーリングラムダを渡す

Passing trailing lambdas

※ trail A<軽い物など>を引きずる

Kotlinでは、簡易として、最後のパラメーターが関数の場合は、カッコの外側にラムダ式を配置することができる。

val product = items.fold(1) { acc, e -> acc * e }

そのような構文を trailing lambda と呼ぶ。

ラムダ式がただ一つの引数の場合は、カッコを省略することができる。

run { println("...") }

it: 暗黙のシングルパラメーター名

ラムダ式が、ただ一つのパラメーターを保つ場合は、 暗黙的にitが宣言される。

この際、パラメーターの宣言と->の宣言は許可されない。

ints.filter { it > 0 } // このリテラルの型は、'(it: Int) -> Boolean

ラムダ式から値を返す

Returning a value from a lambda expression

限定返り構文(the qualified return syntax)を使ってラムダからの値を明示的に返すことができるが、 最後の式の値が暗黙の返り値となる。

以下の2つのスニペットは同一である。

ints.filter {
    val shouldFilter = it > 0
    shouldFilter
}
ints.filter {
    val shouldFilter = it > 0
    return@filter shouldFilter
}

トレーリングラムダと合わせることで、LINQ-Styleコードを取ることができる

strings.filter { it.length > 5 }.sortedBy { it }.map { it.uppercase() }

未使用変数のためのアンダースコア

Underscore for unsued variables

map.forEach { _, value -> println("$value!") }

ラムダ内部の分割代入

Destructing in lambdas

https://kotlinlang.org/docs/destructuring-declarations.html#destructuring-in-lambdas

ラムダパラメーターに対して、分割宣言構文(destructing declarations syntax)を使うことができる。

Pairタイプ(もしくは、Map.Entry型やその他の適切なcomponentN functionsを持つ型)をラムダのパラメーターとして持つ場合は、 複数のパラメーターに分割代入することができる。

map.mapValues { entry -> ${entry.value}" }

上記は、複数のパラメーターに分割できる。

map.mapValues { (key, value) -> "$value" }

匿名関数

匿名関数を使えば、返り値を指定することができる。 (ただし殆どの場合において、不要である。)

fun(x: Int, y: Int): Int = x + y

匿名関数は、通常の関数ととても似ているが、名前が省略されている。 その本体は、式(上記の例)もしくは、ブロックを指定することができる。

fun(x: Int, y: Int): Int = {
    return x + y
}


// 代入は、ブロックでのみ有効
var sum = 0
ints.forEach(fun(v) {
    sum += v
}

// 式内部では、代入は不可
// Assignments are not expressions, and only expressions are allowed in this context
// ints.forEach(fun(v) = sum += v)

パラメーターと返り値の型は同時に宣言することができるが、推測できる場合は、排除できる。

ints.filter(fun(item) = item > 0)

NOTE: 匿名関数は、トレーリングラムダ式のように、外部に配置することはできない。

ラムダ式と匿名関数のもう一つの違いは、ローカルでない返り値の振る舞いである。

ラベルの無いreturn文は、常にfunキーワードを使って宣言された関数から返される。 つまり、 ラムダ式内部のreturnは、内含された関数(the enclosing function)から返されるのに対して、 匿名関数内部のreturnは、匿名関数から返される。

Closures

ラムダ式と匿名関数(ローカル関数かつオブジェクト式)は、そのクロージャーにアクセスすることができる。

クロージャー: 外側のスコープに宣言された変数を含む環境

クロージャー内部でキャプチャーされた変数は、ラムダ式内部で編集することができる。

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

匿名関数を使った場合

var sum = 0
ints.filter(fun(v) = v > 0).forEach(fun(v) {
    sum += v
})
print(sum)
  • 匿名関数本体に式を渡した場合は、代入はできない
// NG
ints.forEach(fun(v) = sum += v)