分解宣言

分解宣言とは

分解宣言(Destructuring Declaration)とは、
あるインスタンスを利用して、複数の変数を同時に宣言することです。
あるインスタンス(のプロパティ)を、いっぺんに複数の変数に分解するようなイメージになります。

fun main(args: Array) {
    val student = Student("suzuki", 80.0)

    // studentをnameとscoreに分解する
    val (name, score) = student

    println(name)
    println(score)
}

data class Student(val name: String, var score: Double) 
suzuki
80.0 

これは、Studentクラスに暗黙的に自動実装される
componentNというメンバーを利用した機能です。
分解宣言は実際には次の処理を行っていることになります。

fun main(args: Array) {
    val student = Student("suzuki", 80.0)

    // 分解線gんの実体は次の処理になる
    val name = student.component1() // nameに対応する、暗黙的に実装されるcomponentN()
    val score = student.component2() // scoreに対応する、暗黙的に実装されるcomponentN()
    
    println(name)
    println(score)
}

data class Student(val name: String, var score: Double) 
suzuki
80.0 

分解実装はさまざまな場面で利用できますが、典型的な使い方はfor文で利用する場合です。

import kotlin.math.sqrt

fun main(args: Array) {
    val list = listOf(
            Point3D(1.0, 2.0, 3.0),
            Point3D(4.0, 5.0, 6.0),
            Point3D(7.0, 8.0, 9.0))

    for ((x, y, z) in list) {
        val distance = sqrt(x * x + y * y + z * z)

        println("Distance: ${distance}")
    }
}

data class Point3D(var x: Double, var y: Double, var z: Double) 
Distance: 3.7416573867739413
Distance: 8.774964387392123
Distance: 13.92838827718412 

クラス (オブジェクト)

オブジェクトとは

多くのプログラミング言語では、「オブジェクト」といえは、
インスタンス化されたクラスの実際のデータを指します。
つまり、インスタンスとオブジェクトは同じ意味で使われています。
しかし、Kotlinでは「オブジェクト」というと別のことを意味します。
Kotlinでは、
「あるクラスをちょっとだけ変更したものを一時的に利用したい場合、
 そのクラスを継承したものをその場で作成する」とか
「一時的にちょっとしたクラスを作って利用したいときにその場でクラスをつくる」
という場合に「オブジェクト」を作成すると言います。
そして、その作成した一時的なクラスを「オブジェクト」と呼びます。
Javaでは「匿名クラス」と呼ばれていた機能に相当します。

オブジェクトの使い方

それではまず「あるクラスをちょっとだけ変更したものを一時的に利用したい場合、
そのクラスを継承したものをその場で作成する」というサンプルを確認します。
この場合は、既存クラスをその場で継承してオーバーライドするということをその場で実施します。

fun main(args: Array) {
    val student1 = Student("suzuki", 80.0)
    student1.describe()

    // その場でStudentの関数をオーバーライドして、少しだけ処理を変更した「オブジェクト」を作成する
    // 基底クラスにプライマリコンストラクタがある場合、その場で値を渡さなければなりません
    val student2 = object : Student("tanaka", 65.0) {
        override fun describe() = print("Name: ${name}\nScore: ${score}\n")
    }

    student2.describe()
}

open class Student(val name: String, var score: Double) {
    open fun describe() = println("Name is ${name} and score is ${score}.")
} 
Name is suzuki and score is 80.0.
Name: tanaka
Score: 65.0 

次に、 「一時的にちょっとしたクラスを作って利用したいときにその場でクラスをつくる」
場合のサンプルを見てみましょう。
こちらは、クラスをその場で実装するような感じで利用します。

fun main(args: Array) {
    // ちょっとしたデータや関数の集まりをその場で用紙できる。
    val p = object {
        val name = "suzuki"
        var score = 55.5

        fun describe() = println("Name: ${name} Score: ${score}")
    }

    p.score = 80.0
    p.describe()
} 
Name: suzuki Score: 80.0 

クラスを構成するオブジェクト

オブジェクトはあくまで「その場限り」つまり「ローカル」なモノです。
関数やプロパティなどのクラスメンバーは返り値としてオブジェクトを返すことができますが、
そのオブジェクトを利用できるのは、そのクラスの内部だけです。
そのためpublicなメンバーの返り値としてオブジェクトを返しても、
その型は自動的に「Any」になるため、オブジェクトのメンバーにアクセスすることができません。

fun main(args: Array) {
    val student = Student("suzuki", 80.0)
    student.testObject()
}

class Student(val name:String, var score: Double) {
    private val description
        get() = object {
            val typeA = "Name: ${name} Score: ${score}"
            val typeB = "Name is ${name} nad score si ${score}"
        }

    fun foo() = object {
        val x = 100
        val y = 200
    }

    fun testObject() {
        // privateメンバーであれば、オブジェクトを期待通りに利用できる
        println(description.typeA)
        println(description.typeB)

        // foo()はpublicなので、オブジェクトを返しても、自動的に「Any」となる。
        // そのため、オブジェクtのメンバーには結局アスセスすることができない
        // 次のコードはビルドエラーになる
        // println(foo().x)
    }
} 
Name: suzuki Score: 80.0
Name is suzuki nad score si 80.0 

オブジェクト宣言

ここまでのオブジェクトは名前のついていない、つまり匿名オブジェクトでした。
しかし、実は「名前付き」のオブジェクトを作成することができます。
これは、いわゆる「シングルトン」として機能します。

fun main(args: Array) {
    // Cache自体を一種の変数のように扱えるた、
    // Cacheは一つしかない
    Cache.add("suzuki")
    Cache.add("tanaka")

    println(Cache.studentList)
}

// オブジェクト宣言(シングルトン)
object Cache {
    val studentList = mutableListOf()

    fun add(name: String) {
        if (!name.isEmpty()) {
            studentList.add(name)
        }
    }
} 
[suzuki, tanaka] 

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

クラスメンバーとしてのオブジェクト宣言の場合、「companion」をつけることで、
そのメンバーの呼出を簡単にすることができるようになります。

fun main(args: Array) {
    School.add("suzuki")
    School.add("tanaka")

    println(School.studentList)
}

// オブジェクト宣言(シングルトン)
class School {
    companion object Cache {
        val studentList = mutableListOf()

        fun add(name: String) {
            if (!name.isEmpty()) {
                studentList.add(name)
            }
        }
    }
} 
[suzuki, tanaka] 

クラス (エクステンション)

エクステンション

既存のクラスの機能を拡張する基本的な方法は、そのクラスを継承して派生クラスを作成することです。
しかし、Kottlinでは継承以外にもクラスの機能を拡張する方法があります。
これを、「エクステンション」といいます。
「エクステンション」て拡張できるのは、クラスの関数とプロパティです。
「エクステンション」を追加するクラスのことを「レシーバ」と呼びます

関数のエクステンション

クラスに関数を追加するためには、次のような記述をします。

fun レシーバ.追加したい関数名(引数): 返り値の型 {
    処理
} 

それでは、サンプルを見てみましょう。

fun main(args: Array) {
    val num = 5

    println("${num}の階乗は、${num.Functional()}")
}

// 整数の階乗を求める関数をInt型にエクステンションとして追加する
fun Int.Functional(): Int {
    // this はそのインスタンス自身をさす。
    if (this > 1) {
        return this * (this - 1).Functional()
    } else {
        return 1
    }
} 

上のサンプルのように、エクステンションの中で、
そのエクステンションを呼び出しているインスタンス自身にアクセスするには、 「this」を使います。

プロパティのエクステンション

プロパティのエクステンションも関数とエクステンションと同じように記述することができます。

val レシーバ.プロパティ名: 型
    get() {
    }

var レシーバ.プロパティ名: 型
    get() {
    }
    set(value) {
    } 

それでは、サンプルを見てみましょう。

fun main(args: Array) {
val num = 5

println("${num}の階乗は、${num.Functional}")
}

val Int.Functional: Int
get() {
if (this > 1) {
return this * (this - 1).Functional
} else {
return 1
}
}

5の階乗は、120 

エクステンションの解決

エクステンションは、あるクラスから「.」で呼び出すことのできる関数やプロパティを追加するだけで、
実際にそのクラスのメンバを追加するわけではありません。
そのことを確認するために、継承とエクステンションの動きの違いを次のサンプルで確認しましょう。

fun main(args: Array) {
    val obj: Base = Derived()

    // objの実体はDerivedなので、Derivedのメンバーが呼び出される
    obj.PrintMember()

    // objの型はBaseなので、Baseのエクステンションが呼び出される
    obj.PrintExtension()
}

open class Base {
    open fun PrintMember() = println("Member of Base is called.")
}

class Derived : Base() {
    override fun PrintMember() = println("Member of Derived is called.")
}

fun Base.PrintExtension() = println("Extensino of Base is called.")

fun Derived.PrintExtension() = println("Extension of Derived is called.") 
Member of Derived is called.
Extensino of Base is called. 

エクステンションは、あくまでそのエクステンションを呼び出している変数の型によって決まります。
そのため、上のサンプルのようにその実体が派生クラスであっても、派生クラスのエクステンションが呼び出されることはありません。

nullableなレシーバ

レシーバがnullableの場合は、少し注意が必要です。
というのも、エクステンションの中で、thisを利用する場合、thisはもちろんnullableなので、
nullチェックのあとでないとメンバを呼び出すことができないからです。

クラス (オーバーライド)

メソッド

Kotlinでは、オーバーライド可能なメンバーには明示的に「open」キーワードを付けなければいけません。
また、子クラスで実際にオーバーライドしているメンバーには「override」キーワードを付ける必要があります。

fun main(args: Array) {
    val student = Student("suzuki", 82.1)

    student.describe()
}

open class Person(name: String) {
    val name = name

    open fun describe() {
        println("Name is ${name}.")
    }
}

class Student(name: String, score: Double) : Person(name) {
    val score = score

    override fun describe() {
        println("Name is ${name}. Score is ${score}.")
    }
} 
Name is suzuki. Score is 82.1. 

オーバーライドしたメンバー自身は、自動的に「open」になります。
(上記の例で言えば、Studentクラスのdescribe関数は、Studentの子クラスでオーバーライド可能です)
もし、これ以上、子クラスでオーバーライドしてほしくなければ、「final」キーワードを付けます。
そうすることで、それ以上のオーバーライドを禁じることができます。

fun main(args: Array) {
    val student = Student("suzuki", 82.1)

    student.describe()
}

open class Person(name: String) {
    val name = name

    open fun describe() {
        println("Name is ${name}.")
    }
}

class Student(name: String, score: Double) : Person(name) {
    val score = score

    // finalをつけるとこれ以上、子クラスでオーバーライドできなくなる
    final override fun describe() {
        println("Name is ${name}. Score is ${score}.")
    }
} 

プロパティ

プロパティもメソッドと同様にオーバーライドすることができます。
プロパティのオーバーライドでは、親クラスで読取り専用の「val」だったものも、子クラスで「var」にオーバーライドスルことができます。
これは、getterしかなかったプロパティにオーバーライドによってsetterを追加することが可能だということです。

fun main(args: Array) {
    val student = Student("suzuki", 82.1)
    student.name = ""

    student.describe()
}

open class Person(name: String) {
    // openをつけることでオーバーライド可能にする
    open val name = name

    open fun describe() {
        println("Name is ${name}.")
    }
}

class Student(name: String, score: Double) : Person(name) {
    override var name = name
        set(value) {
            if (value.isEmpty()) {
                field = "empty"
            } else {
                field = value
            }
        }

    val score = score

    // finalをつけるとこれ以上、子クラスでオーバーライドできなくなる
    final override fun describe() {
        println("Name is ${name}. Score is ${score}.")
    }
} 
Name is empty. Score is 82.1. 

継承時の初期化の順序

子クラスをインスタンス化した時の、初期化の順番は次のとおりになります。

  1. 親クラスのコンストラクタに渡す引数の評価
  2. 親クラスの記述されている初期化処理
  3. 子クラスに記述されている初期化処理
fun main(args: Array) {
    val student = Student("suzuki", 82.1)
}

open class Person(name: String) {
    init {
        println("Base: init block")
    }

    val name = name.also { println("Base: property initializer. ${it}") }
}

class Student(name: String, score: Double) : Person(name.also { println("Derived: Argument for Base Constructor") }) {
    init {
        println("Derived: init block")
    }

    var score = score.also { println("Derived: property initializer. ${it}") }
} 
Derived: Argument for Base Constructor
Base: init block
Base: property initializer. suzuki
Derived: init block
Derived: property initializer. 82.1 

super

子クラスで親クラスのメンバーにアクセスして処理を行ったり、値を設定したりしたい場合があります。
そのような場合には、「super」を使って親クラスのメンバーにアクセスします。

fun main(args: Array) {
    val student = Student("suzuki", 82.1)
    student.name = ""

    student.describe()
}

open class Person(name: String) {
    open val name = name

    open fun describe() {
        println("Name is ${name}.")
    }
}

class Student(name: String, score: Double) : Person(name) {
    override var name = name
        set(value) {
            if (value.isEmpty()) {
                field = "empty"
            } else {
                field = value
            }
        }

    val score = score

    final override fun describe() {
        // superを使って親クラスのメンバーを呼び出す
        super.describe()
        println("Name is ${name}. Score is ${score}.")
    }
} 
Name is empty.
Name is empty. Score is 82.1. 

複数の実装をもつメンバーのオーバーライドルール

クラスやインターフェースを複数継承した場合に、それぞれの親クラスやインターフェースに
同じ名前のメンバーが別々の実装を持っている場合があります。
このような場合には、子クラスでは必ずそのメンバーをオーバーライドして子クラス独自に処理を実装する必要があります。
(superを使っていずれかの親クラスやインターフェースの処理を採用しても、もちろんOKです)

複数の親クラスやインターフェースを継承した場合は、「super<base>」とすることでそれぞれの親クラスやインターフェースにアクセスします。

fun main(args: Array) {
    val student = Student("suzuki", 82.1)

    student.describe()
}

open class Person(name: String) {
    val name = name

    open fun describe() {
        println("Name is ${name}.")
    }
}

interface Describable {
    fun describe() {
        println("dummy")
    }
}

class Student(name: String, score: Double) : Person(name), Describable {
    val score = score

    // describeの実装が複数存在してしまうため、
    // オーバーライドして独自に実装しなおさないとビルドエラーになる
    override fun describe() {
        // それぞれのdescribeにアクセスできる
        super.describe()
        super.describe()
        
        println("Name is ${name}. Score is ${score}.")
    }
} 
Name is suzuki.
dummy
Name is suzuki. Score is 82.1. 

クラス (ヴィジビリティ)

ヴィジビリティ修飾子

クラス、オブジェクト、インターフェース、コンストラクタ、関数、プロパティとそのセッターに対して、ヴィジビリティを設定することができます。
(ゲッターのヴィジビリティはプロパティのヴィジビリティと同じものになります)
Kotlinには、4種類のヴィジビリティ修飾子があります。

  • private
  • protected
  • internal
  • public

明示的にヴィジビリティ修飾子を設定しない場合、デフォルトのヴィジビリティ修飾子は「public」になります。

パッケージ

トップレベルに宣言されたクラス、オブジェクト、インターフェース、関数、プロパティのヴィジビリティは次の表のようになります。

ヴィジビリティ どこから見えるか
public(デフォルト) どこからでも参照可能
private 同じファイル内のみ参照可能
internal 同じモジュール内のみ参照可能
protected トップレベルで宣言したものには使用できない

example.kt

package visibilitytest

// getterにはどこからでも参照できる
var num: Int = 123
    // setterはこのファイル内でしか参照できない
    private set(value) {
        if (value < 0) {
            println("${value} は無効です")
            return
        }

        field = value
    }

// internalなので、この関数は同じモジュールからであれば参照できる
internal fun assignNum(value: Int) {
    num = value
} 

main.kt

package visibilitytest

fun main(args: Array) {
    println("num = ${num}")
    assignNum(-321)

    // numのsetterはprivateなので、以下のコードはビルドエラー
    // num = -321
} 
num = 123
-321 は無効です 

クラス/インターフェースメンバー

クラスとインターフェースのメンバーのヴィジビリティは次の表のようになります.

ヴィジビリティ どこから見えるか
private 同じクラス/インターフェース(のメンバー)から参照可能
protected privateと同じ範囲+サブクラス(のメンバー)から参照可能
internal internalなメンバーを持つクラスが宣言されているモジュール内から参照可能
public(デフォルト) どこからでも参照可能

example.kt

package visibilitytest

import java.nio.DoubleBuffer

open class Person(name: String) {
    var name = name
        protected set(value) {
            if (value.isEmpty()) {
                println("Empty name is invalid")
                return
            }

            field = value
        }

    internal fun printName() = println("Name is ${name}.")
}

class Student(name: String, score: Double) : Person(name) {
    var score = score

    fun toUpperCaseName() {
        // name のsetterはprotectedなので、サブクラスではアクセルできる
        name = name.toUpperCase()
    }
}

class PersonDescriber(someone: Person) {
    val someone = someone

    fun describe() {
        // printName はinternalなので、同じモジュール内では参照できる
        someone.printName()
    }
} 

main.kt

import visibilitytest.Student
import visibilitytest.PersonDescriber

fun main(args: Array) {
    val student = Student("suzuki taro", 80.0)
    println("student is ${student.name}")
    student.toUpperCaseName()

    val desc = PersonDescriber(student)
    desc.describe()
} 
student is suzuki taro
Name is SUZUKI TARO. 

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

プライマリコンストラクタにヴィジビリティを設定したい場合、明示的にconstructorキーワードをつける必要があります。

example.kt

package visibilitytest

// プライマリコンストラクタにprotectedを設定
open class Person protected constructor(name: String) {
    var name = name
        protected set(value) {
            if (value.isEmpty()) {
                println("Empty name is invalid")
                return
            }

            field = value
        }

    internal fun printName() = println("Name is ${name}.")
}

class Student(name: String, score: Double) : Person(name) {
    var score = score

    fun toUpperCaseName() {
        // name のsetterはprotectedなので、サブクラスではアクセルできる
        name = name.toUpperCase()
    }
}

class PersonDescriber(someone: Person) {
    val someone = someone

    fun describe() {
        // printName はinternalなので、同じモジュール内では参照できる
        someone.printName()
    }
} 

main.kt

import visibilitytest.Person
import visibilitytest.Student
import visibilitytest.PersonDescriber

fun main(args: Array) {
    val student = Student("suzuki taro", 80.0)
    println("student is ${student.name}")
    student.toUpperCaseName()

    val desc = PersonDescriber(student)
    desc.describe()

    // Personのプライマリコンストラクタはprotectedであり、ここからはアクセスできない
    // そのため、直接Personをインスタンス化することはできない
    // 以下のコードはビルドエラー
    // val person = Person("someone")
} 

クラス (抽象クラス/インターフェース)

抽象クラス

クラスのメンバーにキーワード「abstract」をつけて宣言することができます。
これを抽象メンバーと言います。
抽象メンバーは処理や値といった具体的な実装を持ちません(持つことができません)
抽象メンバを持つクラスを抽象クラスと呼び、このクラスにも「abstract」キーワードを付ける必要があります。
抽象クラスはインスタンス化することができません。
その理由は、抽象クラスは抽象メンバという具体的な実装を持たないメンバーが含んでいて、
それ単体では不完全な状態だからです。

fun main(args: Array) {
    // Personは抽象クラスなのでインスタンス化できない
    // 以下のコードはビルドエラーになる
    // val someone = Person("someone")
}

abstract class Person(name: String) {
    abstract var name: String
    abstract fun describe()
} 

抽象メンバーは自動的にオーバーライド可能な「open」になります。
抽象クラスを直接インスタンス化することはできませんが、
抽象クラスを検証した派生クラスで抽象メンバーをオーバーライドして具体的な実装を与えることで
利用することができます。

fun main(args: Array) {
    val someone: Person = Student("suzuki")

    someone.describe()
}

abstract class Person(name: String) {
    abstract var name: String
    abstract fun describe()
}

class Student(name: String) : Person(name) {
    override var name = name
    override fun describe() {
        println("name: ${name}")
    }
} 
name: suzuki 

インターフェース

KotlinにはJavaと同じようなインターフェースが存在します。
インターフェースと抽象クラスとの違いは、データを保持できるかどうかです。

fun main(args: Array) {
    val someone: Printable = Student("suzuki")

    someone.Print()
    someone.PrintImpl()
}

interface Printable {
    fun Print() // 何もしないと自動的に「open」で「abstract」
    fun PrintImpl() {
        // インターフェースの関数でも実装することは可能
        println("print impl")
    }
}

class Student(name: String) : Printable {
    val name = name

    override fun Print() {
        println("name = ${name}")
    }
} 
name = suzuki
print impl 

クラス (継承)

Any

Kotlinでは、すべてのクラスは「Any」クラスを継承しています。
特に、親クラスについて指定していないクラスも、暗黙的に「Any」クラスを継承します。
なお、この「Any」はJavaの「java.lang.Object」とは別のクラスです。
「Any」には「equals()」「hashCode()」「toString()」という関数しかありません。

fun main(args: Array) {
    val student = Student()

    if (student is Any) {
        println("student is Any")
    } else {
        println("student is not Any")
    }
}

class Student 
student is Any 

クラスの継承

javaと違い、Kotlinのクラスはデフォルトでは継承させることができません。
つまり、Kotlinのクラスはデフォルトでfinalです。
(javaではデフォルトでは継承でき、finalをつけると継承不可能になります)
クラスを継承可能にするには「open」をクラスにつけます。
「open」なクラスを継承するには、クラスヘッダーでコロンを付けて親クラスを指定します。

fun main(args: Array) { val student = Student("suzuki") if (student is Person) { println("student is Person") } else if (student is Any) { println("student is Any") } else { println("student is not Any") } } open class Person(name: String) class Student(name: String): Person(name)
student is Person 

子クラスがプライマリコンストラクタを持たない場合、親クラスのセカンダリコンストラクタに初期化処理を委譲することもできます。
子クラスのconstructorの後ろにコロンをつけて「super」で親クラスのセカンダリコンストラクタを呼び出します。

fun main(args: Array) {
    val student = Student("suzuki", 15, 80.0)

    println("name = ${student.name}")
    println("age = ${student.age}")
    println("score = ${student.score}")
}

open class Person(name: String) {
    val name = name
    var age = 0

    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }
}

class Student : Person {
    var score = 0.0

    constructor(name: String, age: Int, score: Double) : super(name, age) {
        this.score = score
    }
} 
name = suzuki
age = 15
score = 80.0 

継承をする場合、親クラスのコンストラクタは必ず何らかの形で呼び出す必要があります。

fun main(args: Array) {
}

open class Person

// 親クラスのプライマリコンストラクタを読んでいるためOK
class Student: Person()

// 親クラスのセカンダリコンスtラクタを読んでいるためOK
class Teacher: Person {
    constructor(): super()
}

// 親クラスのプライマリコンストラクタもセカンダリコンストラクタもよんでいないためNG
// class Parent: Person 

クラス (プロパティ)

プロパティの宣言

クラスのボディに「val」キーワードで宣言すると読取り専用で、
「var」キーワードで宣言すると変数としてプロパティを宣言できます。
プロパティは必ず初期化しないといけません。初期化は「プロパティイニシャライザ」か「イニシャライザブロック」で行います。

fun main(args: Array) {
    val suzuki = Student("suzuki", 80.0)

    // nameは読取り専用なので変更できない
    // 下記のコードはビルドエラー
    // suzuki.name = "tanaka"
    suzuki.score = 70.0

    println("name = ${suzuki.name}")
    println("score = ${suzuki.score}")
    println("note = ${suzuki.note}")
}

class Student(name: String, score: Double) {
    val name = name
    var score = score

    // プロパティは必ず初期化しないといけない
    // 次のコードはビルドエラーになる
    // val desc: String

    var note: String

    init {
        // イニシャライザブロックで初期化してもOK
        note = "None"
    }
} 
name = suzuki
score = 70.0
note = None 

ゲッター/セッター

プロパティはゲッター/セッターを持ちます。
上記の例でもインスタンス「suzuki」からプロパティ「score」に値をセットしています。
直接値をセットしているように見えますが、実際にはデフォルトセッターを通じて値がセットされている状態です。
値を取得している部分も、デフォルトゲッターを通じで値を取得しています。
カスタムゲッター/セッターを作成するには「get」「set」を利用します。
カスタムゲッター/セッター内で、そのプロパティ自身にアクセスするには「field」を利用します。
これを「バッキングフィールド」といいます。
(カスタムゲッター/セッターで、自分自身にアクセスするのに自分のプロパティ名を利用すると、
無限にゲッター/セッターを呼び出すことになりスタックオーバーフローになります。
例えば、nameゲッター内でreturn nameとすると再びnameゲッターを呼ぶことになります。
するとnameゲッターでnameゲッターを呼び、またそのnameゲッターでnameゲッターを呼び…
と無限に呼び出してしまします)

fun main(args: Array) {
    val suzuki = Student("suzuki", 80.0)

    suzuki.score = 70.0
    suzuki.score = -10.0

    println("name = ${suzuki.name}")
    println("score = ${suzuki.score}")
    println("note = ${suzuki.note}")
}

class Student(name: String, score: Double) {
    val name = name
        get() = field.toUpperCase()
        // nameは読取り専用なので、setterを作成することはできない

    var score = score
        get() = field / 100.0
        set(value) {
            if (value < 0.0 || 100.0 < value) {
                return
            }

            field = value
        }

    var note: String

    init {
        note = "None"
    }
} 
name = SUZUKI
score = 0.7
note = None 

また、必要に応じてゲッター/セッターにもヴィジビリティ修飾子やアノテーションを指定することができます.

fun main(args: Array) {
    val suzuki = Student("suzuki", 80.0)

    // scoreセッターはprivateなので、以下はビルドエラーになる
    // suzuki.score = 70.0
    // suzuki.score = -10.0

    println("name = ${suzuki.name}")
    println("score = ${suzuki.score}")
    println("note = ${suzuki.note}")
}

class Student(name: String, score: Double) {
    val name = name
        get() = field.toUpperCase()

    var score = score
        get() = field / 100.0
        private set(value) {
            if (value < 0.0 || 100.0 < value) {
                return
            }

            field = value
        }

    var note: String

    init {
        note = "None"
    }
} 
name = SUZUKI
score = 0.8
note = None 

クラス (コンストラクタ)

クラスの宣言とクラスメンバ

クラスは「class」キーワードで宣言します。
クラスは、メンバと呼ばれる以下の要素で構成されます。

メンバ 内容
コンストラクタ
イニシャライザブロック
インスタンスの初期化
関数 Javaでいうメソッド
プロパティ Javaでいうメンバにゲッター/セッターなどの機能をもたせたもの
ネスト/インナークラス クラス内で宣言されたクラス
オブジェクト宣言 シングルトンなどを作成するときに利用

コンストラクタ

クラスにはプライマリコンストラクタとセカンダリコンストラクタという2種類があります。
いずれも必須ではありません。

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

プライマリコンストラクタは「class」キーワードの後ろの方、
つまりクラスヘッダーに「constructor」をつけて記述します。

fun main(args: Array) {
    val student = Student("suzuki", 80.0)
}

              /* この部分がプライマリコンストラクタ      */
class Student constructor(name: String, score: Double) {
} 

なお、クラスのボディ(プロパティなど「{ }」の中身の部分)が不要な場合は、「{ }」を省略できます。

fun main(args: Array) {
    val student = Student("suzuki", 80.0)
}

class Student constructor(name: String, score: Double) 

プライマリコンストラクタの「constructor」部分は省略可能です。
逆に言うと、「constructor」が必要なのは、ヴィジビリティ修飾子(publicなど)やアノテーション(@Injectなど)をつけたい場合です。

fun main(args: Array) {
    val student = Student("suzuki", 80.0)
    val school = School("Kotrin School")
}

class Student(name: String, score: Double)

class School public constructor(name: String) 

さて、この「プライマリコンストラクタ」の使い方ですが、コンストラクタの宣言部にはパラメータを記述することができるだけで、
何かの処理を行うコードを記述することはできません。
では、何かしらの処理をしたい場合どうするのかというと、「init」キーワードで指定された「イニシャライザブロック」にコードを記述します。

fun main(args: Array) {
    val student = Student("suzuki", 80.0)
}

class Student(name: String, score: Double) {
    init {
        println("initializer")
        println("name = ${name}")
        println("score = ${score}")
    }
} 
initializer
name = suzuki
score = 80.0 

また、プロパティイニシャライザとしても、プライマリコンストラクタの変数を利用できます。

fun main(args: Array) {
    val student = Student("suzuki", 80.0)

    println("name length = ${student.nameLength}")
    println("description = ${student.description}")
}

class Student(name: String, score: Double) {
    val nameLength = name.length
    val description = "name: ${name} score: ${score}"

    init {
        println("initializer")
        println("name = ${name}")
        println("score = ${score}")
    }
} 
initializer
name = suzuki
score = 80.0
name length = 6
description = name: suzuki score: 80.0 

また、イニシャライザブロックは分割することもできます。
分割した場合は、単純に上にあるものから順番に実行されていきます。
イニシャライザブロックの中でもプロパティを参照することはできますが、そのイニシャライザブロックより前に宣言されていなければなりません。

fun main(args: Array) {
    val student = Student("suzuki", 80.0)
}

class Student(name: String, score: Double) {
    init {
        println("First Block")
        println("name = ${name}")
        println("score = ${score}")
    }

    val nameLength = name.length

    init {
        println("Second Block")
        println("nameLength = ${nameLength}")

        // 以下のコードはビルドエラー
        // println("descripton = ${description}")
    }

    val description = "name: ${name} score: ${score}"

    init {
        println("Third Bllock")
        println("descripton = ${description}")
    }
} 
First Block
name = suzuki
score = 80.0
Second Block
nameLength = 6
Third Bllock
descripton = name: suzuki score: 80.0 

セカンダリコンストラクタ

また、クラスボディ内でconstructorキーワードを指定することで、セカンダリコンストラクタを宣言することもできます。
プライマリコンストラクタと違い、セカンダリコンストラクタは複数個持つこともできます。

fun main(args: Array) {
    val school = School()
    val suzuki = Student(school)
    val tanaka = Student()

    println("student count = ${school.students.count()}")
}

class Student {
    constructor(school: School) {
        school.students.add(this)
    }

    constructor() {
    }
}

class School {
    public val students = arrayListOf()
} 
student count = 1 

プライマリコンストラクタとセカンダリコンストラクタの両方がある場合、
セカンダリコンストラクタはプライマリコンストラクタに処理を委譲する(プライマリコンストラクタを呼び出す)必要があります。
また、プライマリコンストラクタを直接委譲しなくても、プライマリコンストラクタに委譲している他のセカンダリコンストラクタに委譲しても大丈夫です。
他のコンストラクタへの委譲は「this」キーワードを利用して行います。

fun main(args: Array) {
    val school = School()

    val sato = Student()
    println(sato.nameKey)
    println()
    val suzuki = Student(school)
    println(suzuki.nameKey)
    println()
    val tanaka = Student("tanaka", school)
    println(tanaka.nameKey)
    println()

    println("student count = ${school.students.count()}")
}

class Student(name: String) {
    public val nameKey = name.toUpperCase()

    init {
        println("initializer block")
    }

    constructor(): this("None") {
        // thisでプライマリコンストラクタに委譲している
        println("secondary constructor: 1")
    }

    constructor(school: School): this() {
        // thisでセカンダリコンストラクタ1に委譲している
        println("secondary constructor: 2")
    }

    constructor(name: String, school: School): this(name) {
        // thisでプライマリコンストラクタに委譲している
        println("secondary constructor: 3")
        school.students.add(this)
    }

}

class School {
    public val students = arrayListOf()
} 
initializer block
secondary constructor: 1
NONE

initializer block
secondary constructor: 1
secondary constructor: 2
NONE

initializer block
secondary constructor: 3
TANAKA

student count = 1 

ラムダ その2

匿名関数

ラムダ式では返り値の型を明示的に指定することができません。
明示的に指定できなくても、自動的に推測してくれるため、大抵の場合は問題ありません。
しかし、何らかの事情で返り値の型を明示的に指定したい場合、「匿名関数」を利用することができます。
匿名関数とは、その名前の通り関数名(関数の識別子)を持たない関数のことです。

fun(引数リスト): 返り値の型 = 式

fun(引数リスト): 返り値の型 {
    式
} 

上記のように、関数名がないこと以外は普通の関数と同じです。
それでは、サンプルを見てみましょう。

fun main(args: Array) {
    val fruits = listOf("apple", "banana", "orange", "peach")

    // 関数名の前に::をつけることで関数を参照を取得できる
    val subList = filter(
            fruits,
            fun(str: String): Boolean = str.contains('p', true) )

    println(subList)
}

fun filter(source: List, condition: (String) -> Boolean): List {
    val ret = mutableListOf()

    for (i in source) {
        if (condition(i)) {
            ret.add(i)
        }
    }

    return ret;
} 
[apple, peach] 

関数リテラルと関数型

Kotlinにおいて、ラムダ式と匿名関数は関数リテラルとして扱われます。
数値リテラルや文字列リテラルなどと同じように、変数に代入することができます。

fun main(args: Array) {
    val fruits = listOf("apple", "banana", "orange", "peach")

    // 変数に代入できる
    val anonymous = fun(str: String): Boolean = str.contains('p', true)
    val lamda = { str: String -> str.contains('p', true) }

    val subList = filter(fruits, anonymous)
    println(subList)

    val subListLamda = filter(fruits, lamda)
    println(subListLamda)
}

fun filter(source: List, condition: (String) -> Boolean): List {
    val ret = mutableListOf()

    for (i in source) {
        if (condition(i)) {
            ret.add(i)
        }
    }

    return ret;
} 
[apple, peach]
[apple, peach] 

また、上記のサンプルのfilter関数のcondition引数のように、
引数の型と返り値の型から関数型を指定することができます。

fun main(args: Array) {
    val fruits = listOf("apple", "banana", "orange", "peach")

    // 型を指定して変数を宣言
    val anonymous: (String) -> Boolean
    val lamda: (String) -> Boolean

    anonymous = fun(str: String): Boolean = str.contains('p', true)
    // 引数の型が一つしかないことがわかっているので、itを利用できる
    lamda = { it.contains('p', true) }

    val subList = filter(fruits, anonymous)
    println(subList)

    val subListLamda = filter(fruits, lamda)
    println(subListLamda)
}

fun filter(source: List, condition: (String) -> Boolean): List {
    val ret = mutableListOf()

    for (i in source) {
        if (condition(i)) {
            ret.add(i)
        }
    }

    return ret;
} 

[apple, peach]
[apple, peach]

クロージャ

ラムダ式や匿名関数の変数は、クロージャとして機能しています。
つまり、ラムダ式や匿名関数自身が定義されたスコープにアクセスできます。

fun main(args: Array) {
    val fruits = listOf("apple", "banana", "orange", "peach")

    var str = ""
    fruits.filter {
        it.contains('p', true)
    }.forEach {
        // このラムダ式が定義されてるスコープ、つまり「fun main」のスコープにアクセスできる。
        // そのため、fun mainスコープの変数であるstrを操作できる。
        str += it;
        str += "\n"
    }

    print(str);
} 
apple
peach 
Scroll to top