Kotlin プログラミング言語を学ぶ

Kotlin はさまざまな場所で Android デベロッパーが広く使用されているプログラミング言語です。このトピックは、Kotlin を短期間で使用できるようになるための集中コースとして提供されています。

変数の宣言

Kotlin では、2 種類のキーワード、valvar を使用して変数を宣言します。

  • 値が絶対に変わらない変数には val を使用します。val を使用して宣言した変数に値を再割り当てすることはできません。
  • 値が変わる可能性のある変数には var を使用します。

次の例の count は、10 の初期値を割り当てられた Int 型の変数です。

var count: Int = 10

Int は、整数を表す型です。Kotlin で表現できる数値型は、このほかにも数多くあります。他の言語と同様、数値データによっては ByteShortLongFloatDouble も使用できます。

var キーワードを使用すると、必要に応じて count に値を再割り当てすることができます。たとえば、count の値を 10 から 15 に変更できます。

var count: Int = 10
count = 15

ただし、一部の値は変更が想定されていません。languageName と呼ばれる String について考えてみましょう。常に languageName に「Kotlin」の値が確実に保持されるようにしたい場合は、val キーワードを使って languageName を宣言できます。

val languageName: String = "Kotlin"

これらのキーワードにより、変更可能な対象を明示できます。必要に応じてこれらを活用してください。変数参照を再割り当て可能にする必要がある場合は、var として宣言します。それ以外の場合は、val を使用します。

型の推論

引き続き前の例について考えてみましょう。初期値を languageName に割り当てると、Kotlin コンパイラは、割り当てられた値の型に基づいて型を推論できます。

"Kotlin" の値が String 型なので、コンパイラは、languageNameString であると推論します。Kotlin は、静的に型付けされた言語です。つまり、型はコンパイル時に解決され、変更されることはありません。

次の例では、languageNameString と推論されるので、String クラスに含まれない関数を呼び出すことはできません。

val languageName = "Kotlin"
val upperCaseName = languageName.toUpperCase()

// Fails to compile
languageName.inc()

toUpperCase() は、String 型の変数でのみ呼び出し可能な関数です。Kotlin コンパイラが languageNameString として推論しているので、toUpperCase() を安全に呼び出すことができます。ただし、inc()Int 演算子関数なので、String では呼び出せません。Kotlin では、簡潔性と型の安全性を両立させる方法で、型の推論が行われます。

null の安全性

一部の言語では、初期の明示的な値を指定せずに参照型変数を宣言できます。このようなケースでは、通常、変数に null 値が含まれます。Kotlin 変数は、デフォルトでは null 値を保持できません。つまり、次のスニペットは無効です。

// Fails to compile
val languageName: String = null

変数に null 値を保持させるには、null 許容型にする必要があります。次の例のように、型の後ろに ? を付けることで、変数を null 許容として指定できます。

val languageName: String? = null

String? 型では、String 値または null のいずれかを languageName に割り当てることができます。

null 許容変数は、慎重に取り扱わないと NullPointerException が発生するリスクがあります。たとえば、Java では、null 値でメソッドを呼び出そうとすると、プログラムがクラッシュします。

Kotlin には、null 許容変数を安全に処理するためのさまざまなメカニズムが用意されています。詳細については、Android での一般的な Kotlin パターン: null 許容性をご覧ください。

条件

Kotlin は、条件付きロジックを実装するためのメカニズムをいくつか備えています。このうち最も一般的なのは if-else ステートメントです。if キーワード横のかっこ内の式が true として評価されると、そのブランチ内のコード(つまり、その直後の中かっこ内のコード)が実行されます。それ以外の場合は、else ブランチ内のコードが実行されます。

if (count == 42) {
    println("I have the answer.")
} else {
    println("The answer eludes me.")
}

else if を使用して複数の条件を表すことができます。これによって、次の例のように、1 つの条件文で、複雑なロジックを詳細に表すことができます。

if (count == 42) {
    println("I have the answer.")
} else if (count > 35) {
    println("The answer is close.")
} else {
    println("The answer eludes me.")
}

条件文は、ステートフル ロジックを表現するのに便利ですが、それを記述する際には繰り返しが多くなる場合があります。上記の例では、各ブランチにひたすら String を出力しています。この繰り返しを避けるために、Kotlin では条件式が提供されています。最後の例は次のように書き換えることができます。

val answerString: String = if (count == 42) {
    "I have the answer."
} else if (count > 35) {
    "The answer is close."
} else {
    "The answer eludes me."
}

println(answerString)

各条件ブランチは、暗黙的に、式の結果を最終行で返します。したがって、return キーワードを使用する必要はありません。3 つのブランチすべての結果が String 型であるため、if-else 式の結果も String 型になります。この例では、if-else 式の結果から初期値が answerString に割り当てられます。型の推論を使用すると、answerString の明示的な型の宣言を省略できます。ただし、明瞭性を期するためにはそれを含めるようおすすめします。

条件式が複雑になってしまう場合には、次の例のように、if-else 式を when 式に置き換えることをご検討ください。

val answerString = when {
    count == 42 -> "I have the answer."
    count > 35 -> "The answer is close."
    else -> "The answer eludes me."
}

println(answerString)

when 式内の各ブランチは、条件、矢印(->)、結果で表されます。矢印の左側の条件が true として評価されると、右側の式の結果が返されます。あるブランチから次のブランチへと実行されていくわけではないので、ご注意ください。このサンプル式 when のコードは、機能的には前述の例のコードと同等ですが、おそらく読みやすさが向上しています。

Kotlin の条件で特筆すべき強力な機能の一つに、スマート キャスティングがあります。safe-call 演算子または not-null アサーション演算子を使用して null 許容値を扱うのではなく、条件文を使用して、null 値への参照が変数に含まれているか確認できます。次の例をご覧ください。

val languageName: String? = null
if (languageName != null) {
    // No need to write languageName?.toUpperCase()
    println(languageName.toUpperCase())
}

条件ブランチ内では languageName を null 非許容として扱うことができます。Kotlin のスマート機能により、languageName に null 値が保持されていないことがブランチ実行の条件であると認識できるため、そのブランチ内で languageName を null 許容として扱う必要はありません。このスマート キャスティングは、null チェックや型チェック、あるいは contract を満たすあらゆる条件に使用できます。

関数

1 つ以上の式をグループ化して 1 つの関数とすることができます。結果が必要になるたびに、同じ一連の式を繰り返すのではなく、式を関数にラップしてその関数を呼び出すことができます。

関数を宣言するには、fun キーワードに続けて関数名を指定します。次に、関数が取る入力の型があればそれを定義し、返される出力の型を宣言します。関数の本文では、関数呼び出し時に呼び出される式を定義します。

前の例から発展させた場合、完全な Kotlin 関数は以下のようになります。

fun generateAnswerString(): String {
    val answerString = if (count == 42) {
        "I have the answer."
    } else {
        "The answer eludes me"
    }

    return answerString
}

上記の例の関数には、generateAnswerString という名前があります。入力は取りません。String 型の結果を出力します。関数を呼び出すには、関数名を使用し、その後に呼び出し演算子(())を続けます。次の例では、answerString 変数が、generateAnswerString() の結果で初期化されます。

val answerString = generateAnswerString()

次の例に示すように、関数では入力として引数を取ることができます。

fun generateAnswerString(countThreshold: Int): String {
    val answerString = if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }

    return answerString
}

関数を宣言するときは、任意の数の引数とその型を指定できます。上記の例では、generateAnswerString() が、countThreshold という名前の Int 型の引数を 1 つ取っています。関数内では、引数名を使用して引数を参照できます。

この関数を呼び出すときは、関数呼び出しのかっこ内に引数を含める必要があります。

val answerString = generateAnswerString(42)

関数宣言の簡略化

generateAnswerString() は非常にシンプルな関数です。関数は変数を宣言し、すぐに値を返します。次の例に示すように、関数から単一式の結果が返される場合は、関数に含まれる if-else 式の結果を直接返すことで、ローカル変数の宣言をスキップできます。

fun generateAnswerString(countThreshold: Int): String {
    return if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }
}

また、リターン キーワードを割り当て演算子に置き換えることもできます。

fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
        "I have the answer"
    } else {
        "The answer eludes me"
    }

匿名関数

すべての関数が名前を必要とするわけではありません。関数の中には、入力と出力によって直接的に識別されるものがあります。このような関数を匿名関数と呼びます。匿名関数への参照を保存しておき、後でこの参照を使用して匿名関数を呼び出すことができます。また、他の参照型と同様に、参照をアプリケーション周辺に渡すこともできます。

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

名前付き関数と同様、匿名関数には任意の数の式を含めることができます。最後の式の結果が、関数の戻り値になります。

上記の例では、stringLengthFunc に匿名関数への参照が含まれています。この匿名関数は、入力として String を取り、入力 String の長さを Int 型の出力として返します。そのため、関数の型は (String) -> Int と示されます。ただし、このコードでは関数は呼び出されません。関数の結果を取得するには、名前付き関数と同様に、関数を呼び出す必要があります。次の例のように、stringLengthFunc を呼び出すときは String を指定する必要があります。

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

val stringLength: Int = stringLengthFunc("Android")

高階関数

ある関数が別の関数を引数として取ることが可能です。他の関数を引数として使用する関数を、高階関数と呼びます。このパターンは、Java でコールバック インターフェースを使用するのと同じような方法でコンポーネント間通信を行うのに役立ちます。

高階関数の例を次に示します。

fun stringMapper(str: String, mapper: (String) -> Int): Int {
    // Invoke function
    return mapper(str)
}

stringMapper() 関数が、String と合わせて関数を取り、その関数は、渡される String から Intの値を導き出します。

他の入力パラメータを満たす関数(つまり String を入力として取り Int を出力する関数)を、String と合わせて渡すことで、stringMapper()を呼び出すことができます。次の例をご覧ください。

stringMapper("Android", { input ->
    input.length
})

ある関数で定義される最後のパラメータが匿名関数である場合は、関数の呼び出しに使用するかっこの外で、それを渡すことができます。次の例をご覧ください。

stringMapper("Android") { input ->
    input.length
}

匿名関数は、Kotlin 標準ライブラリのさまざまな箇所で使用されています。詳細については、高階関数とラムダをご覧ください。

クラス

ここまで説明してきた型はすべて、Kotlin プログラミング言語に組み込まれています。独自のカスタム型を追加したい場合は、次の例に示すように、class キーワードを使ってクラスを定義できます。

class Car

プロパティ

クラスはプロパティを使用して状態を表します。プロパティとは、ゲッター、セッター、バッキング フィールドを含むことのできるクラスレベルの変数です。 車を走らせるには車輪が必要なので、Wheel オブジェクトのリストを Car のプロパティとして追加できます。次の例をご覧ください。

class Car {
    val wheels = listOf<Wheel>()
}

wheelspublic val です。つまり、wheelsCar クラス外部からアクセス可能であり、再割り当てができません。Car のインスタンスを取得する場合は、まずそのコンストラクタを呼び出す必要があります。そこから、アクセス可能なプロパティにアクセスできます。

val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car

車輪をカスタマイズする場合は、クラス プロパティの初期化方法を指定するカスタム コンストラクタを定義できます。

class Car(val wheels: List<Wheel>)

上記の例のクラス コンストラクタは、List<Wheel> をコンストラクタ引数として取り、その引数を使用して wheels プロパティを初期化しています。

クラス関数とカプセル化

クラスは、関数を使用して動作をモデル化します。関数で状態を変更できます。これは、公開したいデータのみを公開するのに役立ちます。このアクセス制御は、カプセル化と呼ばれる、より大きなオブジェクト指向の概念に含まれています。

次の例では、doorLock プロパティが、Car クラス外部のあらゆるものから非公開とされています。車のロックを解除するには、次の例に示すように、有効なキーを渡す unlockDoor() 関数を呼び出す必要があります。

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

プロパティの参照方法をカスタマイズしたい場合は、カスタムのゲッターとセッターを指定できます。たとえば、プロパティのゲッターを公開する一方で、セッターへのアクセスは制限したい場合、そのセッターを private として申告できます。

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    var gallonsOfFuelInTank: Int = 15
        private set

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

プロパティと関数を組み合わせることで、すべての型のオブジェクトをモデル化するクラスを作成できます。

相互運用性

Kotlin の最も重要な機能の一つに、Java とのスムーズな相互運用性があります。Kotlin コードは JVM バイトコードにコンパイルされるため、Kotlin コードで Java コードに直接呼び出すことや、その逆方向の直接呼び出しが可能です。つまり、既存の Java ライブラリを Kotlin から直接利用できます。さらに、Android API のほとんどは Java で記述されており、Kotlin から直接呼び出すことが可能です。

次のステップ

Kotlin は柔軟な実用的言語であり、支持と勢いが広がりつつあります。まだお試しでなければ使ってみることをおすすめします。次のステップについては、Kotlin の公式ドキュメントと、Android アプリで一般的な Kotlin パターンを適用する方法に関するガイドをご覧ください。