KotlinDive

プログラミング言語 Kotlin についての入門ブログです。

Kotlin入門

1. Kotlin Diveについて

このブログ「Kotlin Dive」は、プログラミング言語「Kotlin」の入門サイトです。

2. 目次

Kotlinについて

開発環境とHello World!(準備中)

忙しい人のための基本的なコードスニペット集

標準コーディングスタイル

データ型(数値、真偽値、配列)

データ型(文字、文字列)

基本的な演算

関数

コレクション(List、Set、Map)

条件分岐(if、when)

繰り返し(for、while)

レンジ

null安全

型チェックとキャスト

ラムダ式の基本

ラムダ式と匿名関数

例外

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

クラス(プロパティ)(準備中)

クラス(継承とインターフェース)(準備中)

クラス(ヴィジビリティ)(準備中)

ジャンプ

ジャンプとは

ジャンプとは、分岐や繰り返しなどの制御フローの途中に挿入することで、 関数やループを途中で抜けたり、あるいは特定の条件のときにループをスキップしたりすることができます。
Kotlinには制御をジャンプするために、次の式が用意されています。

説明
return デフォルトでは、returnを含む最も内側の関数、匿名関数から制御を返す
break そのbreakを含む最も内側のループを終了する
continue そのcontinueを含む最も内側のループに対して、そのループをスキップして次のループにすすめる
 fun main(args: Array) {
    // ブレーク、コンティニューの条件をラムダ式で指定する
    val isBreak = { i: String -> i == "BreakStr" }
    val isContinue = { i: String -> i == "ContinueStr"}

    testLoop(null, isBreak, isContinue)
    testLoop(listOf("suzuki", "tanaka", "BreakStr", "yamada"), isBreak, isContinue)
    testLoop(listOf("suzuki", "tanaka", "ContinueStr", "yamada"), isBreak, isContinue)
}

fun testLoop(list: List?, isBreak: (String) -> Boolean, isContinue: (String) -> Boolean) {
    println("-------------------")
    if (list == null) {
        // listがnullの場合はこの関数を抜ける
        println("Return!")
        return
    }

    for (i in list) {
        if (isBreak(i)) {
            // ブレーク条件をみたす場合は、ループを終了する
            println("Break!")
            break
        }

        if (isContinue(i)) {
            // コンティニュー条件を満たす場合は、今回分の処理をスキップして、次の分の処理に進む
            println("Continue!")
            continue
        }

        println(i)
    }
}
 -------------------
Return!
-------------------
suzuki
tanaka
Break!
-------------------
suzuki
tanaka
Continue!
yamada

ラベル付きbreak/continue

ループにラベルをつけることで、breakやcontimueで特定のラベルのループを抜けることができます。 ラベル@ラベル名で指定します。 これは、多重にネストしたループを一気に抜けたりする場合に便利です

 fun main(args: Array) {
    val data = listOf(1, 2, 3,
                            4, 5, 6,
                            7, 0, 8,
                            9, 10, 11,
                            12, -1, 13,
                            14, 15, 16)

    labelLoop(data, 3, 6)
}

fun labelLoop(data: List, width: Int, height: Int) {
    if (data.size != width * height) return

    outerLoop@ for (y in 0 until height) {
        for (x in 0 until width) {
            val d = data[x + y * width]
            if (d < 0) break@outerLoop
            if (d == 0) {
                print("\n")
                continue@outerLoop
            }

            print("${d} ")
        }

        print("\n")
    }
}
 1 2 3 
4 5 6 
7 
9 10 11 
12 

ラベル付きreturn

Kotlinでは、関数はネスト可能です。 通常のreturnは、そのreturnを囲む一番内側の(匿名)関数をリターンします。
次の例を見てみましょう。

 fun main(args: Array) {
    outerFunc()
}

fun outerFunc() {
    println("Called outerFunc")

    fun innerFunc(value: Int) {
        println("   Called innerFunc")

        if (value < 0) {
            println("   Return innerFunc")
            // このリターンはinnerFuncを終了する
            // outerFuncは終了しない
            return
        }

        println("   End of innerFunc")
    }

    innerFunc(10)
    innerFunc(-5)

    println("End of outerFunc")
}

この、そのreturnを囲むというのが特に重要な意味を持つのは、 ラムダ式の中にreturnがある場合です。 次のサンプルの動作を確認してみましょう。

 fun main(args: Array) {
    outerFunc(listOf("suzuki", "yamada", "ReturnStr", "tanaka"))
}

fun outerFunc(list: List) {
    println("Called outerFunc")

    val outerVal = "outerFuncで宣言された変数"
    list.also {
        println("ラムら式はクロージャなので、${outerVal}にアクセスできる。")
    }.forEach {
        // このreturnは、outerFuncを直接リターンする
        if (it == "ReturnStr") return
        println(it)
    }

    println("End of outerFunc")
}
 Called outerFunc
ラムら式はクロージャなので、outerFuncで宣言された変数にアクセスできる。
suzuki
yamada

もし、ラムダ式の中のreturnラムダ式をリターンしたい場合は、ラベルを付ける必要があります。

 fun main(args: Array) {
    outerFunc(listOf("suzuki", "yamada", "ReturnStr", "tanaka"))
}

fun outerFunc(list: List) {
    println("Called outerFunc")

    val outerVal = "outerFuncで宣言された変数"
    list.also {
        println("ラムら式はクロージャなので、${outerVal}にアクセスできる。")
    }.forEach returnLamda@{
        // このreturnは、このラムダ式だけをリターンできる
        if (it == "ReturnStr") return@returnLamda
        println(it)
    }

    println("End of outerFunc")
}
 Called outerFunc
ラムら式はクロージャなので、outerFuncで宣言された変数にアクセスできる。
suzuki
yamada
tanaka
End of outerFunc

上記の例のように、明示的にラベルを作成しなくても、暗黙的ラベルを利用するとより簡単にリターンすることもできます。

 fun main(args: Array) {
    outerFunc(listOf("suzuki", "yamada", "ReturnStr", "tanaka"))
}

fun outerFunc(list: List) {
    println("Called outerFunc")

    val outerVal = "outerFuncで宣言された変数"
    list.also {
        println("ラムら式はクロージャなので、${outerVal}にアクセスできる。")
    }.forEach {
        // 暗黙的ラベルを利用する
        if (it == "ReturnStr") return@forEach
        println(it)
    }

    println("End of outerFunc")
}
 Called outerFunc
ラムら式はクロージャなので、outerFuncで宣言された変数にアクセスできる。
suzuki
yamada
tanaka
End of outerFunc

クラス(列挙型)

列挙型の使い方

列挙型の最も基本的な使い方は次のようになります。

 fun main(args: Array) {
    printState(State.Run)
}

// 列挙型の宣言
enum class State {
    Stop, Run, Suspend
}

fun printState(state: State) = when(state) {
    State.Stop-> println("Stop")
    State.Run -> print("Run")
    State.Suspend -> println("Suspend")
}
 Run

列挙型のそれぞれの要素を初期化したい場合は次のようにします。

 fun main(args: Array) {
    // 予め用意されているメンバーもあります
    println(State.Run.name)

    println(State.Run.desc)
}

// 列挙型の初期化
// コンストラクタを宣言して、各要素の宣言時に値を渡す
enum class State(val desc: String) {
    Stop("Stop state. This is Initial State."),
    Run("Run state. Something is running."),
    Suspend("Suspend state. This can resume immideately.")
}
 Run
Run state. Something is running.

要素ごとに個別の処理を実装する

Kotlinの列挙型では、それぞれの要素をその列挙型を継承した匿名クラスのように実装することができます。
そうすることで、その列挙型の各要素に、それぞれの要素ごとの処理をわかりやすく実装することができます。 次のサンプルを見てましょう。

 fun main(args: Array) {
    var state = State.Stop

    println(state.name)
    state = state.trans(true, false)
    println(state.name)
    state = state.trans(false, true)
    println(state.name)
    state = state.trans(true, false)
    println(state.name)
    state = state.trans(true, false)
    println(state.name)
}

enum class State() {
    // Stop要素をState列挙型を継承した匿名クラスのように実装する
    Stop {
        // 基底クラスであるStateのメンバーであるtransをオーバーライドする
        override fun trans(isRunBottunPressed: Boolean, isSuspendButtonPressed: Boolean): State {
            if (isRunBottunPressed) {
                // 停止中に実行ボタンが押されたら実行する
                return Run
            }

            // 停止中に一時停止ボタンが押されてもなにもしない

            return Stop
        }
    },

    Run {
        override fun trans(isRunBottunPressed: Boolean, isSuspendButtonPressed: Boolean): State {
            if (isRunBottunPressed) {
                // 実行中に再度実行ボタンを押すと停止する
                return  Stop
            }

            if (isSuspendButtonPressed) {
                // 実行中に一時停止ボタンを押すと一時停止する
                return Suspend
            }

            return Run
        }
    },

    Suspend {
        override fun trans(isRunBottunPressed: Boolean, isSuspendButtonPressed: Boolean): State {
            if (isRunBottunPressed) {
                // 一時停止中に実行ボタンを押すと、再開する
                return Run
            }

            // 一時停止中にもう一度一時停止ボタンを押しても何もおこらない

            return Suspend
        }
    };

    abstract fun trans(isRunBottunPressed: Boolean, isSuspendButtonPressed: Boolean): State
}
 Stop
Run
Suspend
Run
Stop

クラス(オブジェクト)

オブジェクトとは

多くのプログラミング言語では、「オブジェクト」といえは、 インスタンス化されたクラスの実際のデータを指します。 つまり、インスタンスとオブジェクトは同じ意味で使われています。
しかし、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]

リフレクション

リフレクションとは

リフレクションとは、プログラム内で宣言したクラスや関数、プロパティなどから、 それら自身の名前などの情報をプログラムの実行時に取得することです。
Kotlinでは、主に「::」を使ってリフレクションを取得します。

クラス

クラスのリフレクションを取得するには、クラスそのものやオブジェクトの後ろに「::class」をつけます。
「::class」には様々なメンバーがあり、対象となったクラスのいろいろな情報を取得することができます。

import kotlin.reflect.full.declaredMembers

fun main(args: Array) {
    println(Student::class)

    for (parameter in Student::class.declaredMembers) {
        println(parameter)
    }

    println("")

    // インスタンスからもリフレクションを取得可能
    val obj = Student("suzuki", 82.1)

    for (i in obj::class.members) {
        println(i)
    }
}

data class Student(val name: String, var score: Double)
 class Student
val Student.name: kotlin.String
var Student.score: kotlin.Double
fun Student.component1(): kotlin.String
fun Student.component2(): kotlin.Double
fun Student.copy(kotlin.String, kotlin.Double): Student
fun Student.equals(kotlin.Any?): kotlin.Boolean
fun Student.hashCode(): kotlin.Int
fun Student.toString(): kotlin.String

val Student.name: kotlin.String
var Student.score: kotlin.Double
fun Student.component1(): kotlin.String
fun Student.component2(): kotlin.Double
fun Student.copy(kotlin.String, kotlin.Double): Student
fun Student.equals(kotlin.Any?): kotlin.Boolean
fun Student.hashCode(): kotlin.Int
fun Student.toString(): kotlin.String

関数

ある関数を宣言した時、もちろんその関数を「test()」のような形で普通に呼び出すことができます。
一方、その関数自体を一種の「値」として別の関数に渡したい場合などは、「::test」とすることで、「test」関数への参照を取得することができます。
これは関数のリフレクションなので、関数自体の情報、つまり関数名や引数について知ることができます。

 fun main(args: Array) {
    val studentList = listOf("suzuki", "tani", "tanaka", "seki")

    // リフレクションで関数事態の情報を取得
    println(::checkLength.name)
    println(::checkLength.parameters)

    // 関数事態を一種の値として関数に渡す
    println(studentList.filter(::checkLength))
}

fun checkLength(str: String) = str.length > 5
 checkLength
[parameter #0 str of fun checkLength(kotlin.String): kotlin.Boolean]
[suzuki, tanaka]

クラス(データクラス)

Data Transfer Object

アプリを作成していると、単純にひとまとまりのデータを保持しておきたい場合があります。 たとえば、データベースから関連するデータを取り出して(いはゆるモデル層)、画面に表示する(いわゆるビュー層)場合などです。 このように、モデルやビューなどのことなるレイヤーの間でデータを受け渡しするのに利用するオブジェクトを「Data Transfer Object」と呼びます。
Kotlinでは、このようにデータを保持することを目的としたクラスを簡単に作成することができます。 これを、「データクラス」と呼びます。

データクラス

それでは、データクラスを実際に宣言してみましょう。 データクラスを宣言するためには、classの前に「data」をつけます。
また、データクラスは次の条件を満たしている必要があります。

  • プライマリコンストラクタに一つ以上のパラメータがある
  • プライマリコンストラクタのパラメータには「val」か「var」を指定する
  • 「abstract」「open」「inner」「sealed」ではない

fun main(args: Array) {
    var student = Student("suzuki", 82.1)
    println(student.toString())
}

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

Student(name=suzuki, score=82.1)

上記のサンプルのように、データクラスは次のメンバーを自動で生成します。

  • equals()
  • hashCode()
  • toString()
  • copy()
  • componentN()

「componentN()」関数は、宣言順のプロパティに相当します。 例えば、上記のサンプルの「Student」クラスでは、component1()がnameプロパティ、component2()がscoreプロパティに当たります。

「copy()」関数は、上記のサンプルを例にとると次のように宣言されています。


fun copy(name: Stirng = this.name, score: Double = this.score) = Student(name, score) 

そのため、コピーをするときにパラメータを指定することで、必要に応じてパラメータの一部を変更してコピーすることができます。


fun main(args: Array) {
    var student = Student("suzuki", 82.1)
    println(student.toString())

    var student2 = student.copy(name = "tanaka")
    println(student2.toString())
}

data class Student(val name: String, var score: Double) {
    // copyは、具体的には以下のように自動実装される
    // すでに実装されているので、コメントを外すとビルドエラーになる
    // fun copy(name: Stirng = this.name, score: Double = this.score) = Student(name, score)
}

Student(name=suzuki, score=82.1)
Student(name=tanaka, score=82.1)

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

エクステンション

既存のクラスの機能を拡張する基本的な方法は、そのクラスを継承して派生クラスを作成することです。 しかし、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チェックのあとでないとメンバを呼び出すことができないからです。