cv.cm

コーディング · プログラミング言語

Kotlin入門:初心者向けチュートリアル

約12分で読めます

結論

KotlinはJetBrainsが開発した、静的型付けのモダンな言語です。JVM上で動作し、Android開発の推奨言語になっています。簡潔に書けてJavaと完全な相互運用性があり、型システムによってnullポインタエラーの大半をコンパイル時に防げます。このページでは、環境構築、変数、null安全、制御構文、関数、コレクション、クラス、コルーチン、エラー処理を、実行できる例とともに紹介します。

Kotlinとは何か、何に使われるのか

Kotlinは、JetBrainsが開発した静的型付けの汎用言語です。Java仮想マシン(JVM)上で動作するほか、JavaScript、WebAssembly、ネイティブコードにもコンパイルできます。KotlinはJavaと完全な相互運用性があり、KotlinのファイルからあらゆるJavaライブラリを呼び出せ、JavaのコードからKotlinを呼び出すこともできます。

Googleは2019年からKotlinをAndroidアプリ開発の推奨言語としており、新しいAndroidのコードの大半はKotlinで書かれています。サーバーサイドでもSpring BootやKtorなどのフレームワークで広く使われています。また、Kotlin Multiplatformを使えば、Android、iOS、デスクトップ、Webの間でビジネスロジックを共有できます。

KotlinのインストールとHello, worldの実行

最も手軽なのは、Kotlinが組み込まれているIntelliJ IDEAかAndroid Studioを使う方法です。コマンドラインで使う場合は、コンパイラ(SDKMAN!やHomebrewなどで導入)とJavaランタイム(JDK)をインストールします。Kotlinのプログラムはトップレベルの main 関数から始まり、クラスは不要です。

// hello.kt
fun main() {
    println("Hello, world!")
}
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar
# Hello, world!

実際のプロジェクトでは、Gradle(Kotlin DSL)などのビルドツールを使うのが一般的です。インストール不要で試したい場合は、オンラインのKotlin Playgroundも利用できます。

変数と基本の型

読み取り専用の変数は val、変更可能な変数は var で宣言します。型はコンパイラが推論しますが、コロンの後ろに明示することもできます。コードの見通しが良くなるため、なるべく val を使いましょう。

val name = "Ada"          // String, cannot be reassigned
var count: Int = 0        // explicit type, can change
count += 1
val pi = 3.14159          // Double
val big = 9_000_000_000L  // Long
println("$name has $count item(s), pi is ${"%.2f".format(pi)}")
// Ada has 1 item(s), pi is 3.14
型例補足
Int4232ビット符号付き整数
Long42L64ビット符号付き整数
Double3.1464ビット浮動小数点数
Booleantruetrue または false
Char'k'1文字の文字
String"text"変更不可。$name のようなテンプレートが使える

Kotlinには暗黙の数値の拡張変換がなく、Int は自動的に Long に変換されません。toLong()、toDouble()、toString() を明示的に呼び出してください。

null安全

null安全はKotlinを代表する機能です。String のような通常の型は null を保持できません。nullを許可するには String? のように ? を付けます。すると、コンパイラは値を使う前にnullの場合の処理を書くよう強制します。

fun greet(name: String?) {
    // println(name.length)        // compile error: name may be null
    println(name?.length)           // safe call: prints null if name is null
    val len = name?.length ?: 0     // Elvis operator: default value
    if (name != null) {
        println(name.uppercase())   // smart cast: name is String here
    }
    println("length = $len")
}
fun main() {
    greet("kotlin")   // 6, KOTLIN, length = 6
    greet(null)       // null, length = 0
}

制御構文:if、when、ループ

Kotlinでは if と when は式なので、値を返します。when はJavaやCの switch 文に代わるもので、値、範囲、型に対してマッチさせられます。

val score = 82
val grade = if (score >= 50) "pass" else "fail"
val letter = when (score) {
    in 90..100 -> "A"
    in 80..89 -> "B"
    in 70..79 -> "C"
    else -> "F"
}
println("$grade $letter")         // pass B
for (i in 1..3) print("$i ")      // 1 2 3
println()
for (i in 10 downTo 0 step 5) print("$i ")  // 10 5 0
println()
var n = 3
while (n > 0) { print(n); n-- }   // 321
println()

1..3 は両端を含む閉区間です。0 until 3(または 0..<3)は終端を含まないので、インデックスの走査には通常こちらが適しています。

関数とラムダ

関数は fun で宣言します。引数にはデフォルト値を指定でき、呼び出し側は名前付き引数を使えます。本体が1つの式だけなら = で書けます。

fun area(width: Double, height: Double = 1.0): Double = width * height
fun main() {
    println(area(3.0, 2.0))          // 6.0
    println(area(width = 4.0))       // 4.0 (default height)
    val square = { x: Int -> x * x } // a lambda stored in a variable
    println(square(5))               // 25
    val nums = listOf(1, 2, 3, 4, 5)
    val evensSquared = nums.filter { it % 2 == 0 }.map { it * it }
    println(evensSquared)            // [4, 16]
}

ラムダの引数が1つの場合は、it で参照できます。関数の最後の引数がラムダなら、丸括弧の外に出して書けます。そのため filter { ... } は組み込みのブロック構文のように読めます。

コレクションとデータクラス

標準ライブラリでは、読み取り専用のコレクションと変更可能なコレクションが区別されています。listOf、setOf、mapOf は読み取り専用、mutableListOf、mutableSetOf、mutableMapOf は変更可能なコレクションを作ります。

val fruits = mutableListOf("apple", "banana")
fruits.add("cherry")
val ages = mapOf("Ann" to 31, "Bo" to 25)
println(fruits.size)              // 3
println(ages["Ann"])              // 31
println(ages["Zed"])              // null (map lookups are nullable)
data class User(val name: String, val age: Int)
val u = User("Ann", 31)
val older = u.copy(age = 32)
println(u)                        // User(name=Ann, age=31)
println(u == User("Ann", 31))     // true (structural equality)
val (n, a) = older                // destructuring
println("$n $a")                  // Ann 32

data class には、equals、hashCode、toString、copy と分割代入(デストラクチャリング)が自動的に用意されます。Kotlinの == は内容を比較し(equals を呼び出します)、=== は参照を比較します。

クラス、拡張関数、コルーチン

クラスはデフォルトで継承不可(final)です。継承を許可するには open を付けます。拡張関数を使うと、既存の型を変更せずにメソッドを追加できます。sealed クラスは閉じた場合分けを表し、when で網羅性をチェックできます。

fun String.shout(): String = uppercase() + "!"
sealed class Shape
data class Circle(val r: Double) : Shape()
data class Rect(val w: Double, val h: Double) : Shape()
fun area(s: Shape): Double = when (s) {
    is Circle -> Math.PI * s.r * s.r
    is Rect -> s.w * s.h
}   // no else needed: the compiler knows all cases
fun main() {
    println("hi".shout())                   // HI!
    println(area(Rect(2.0, 3.0)))           // 6.0
}

非同期・並行処理には、kotlinx.coroutines ライブラリのコルーチンを使います。suspend 関数はスレッドをブロックせずに一時停止でき、launch や async などのビルダーがスコープ内でコルーチンを開始します。Androidアプリがメインスレッドとは別にネットワークI/Oを行うのも、この仕組みです。

エラー処理

Kotlinには、Javaと同じように try、catch、finally がありますが、例外はすべて非チェック例外で、宣言や捕捉を強制されることはありません。try も式として使えます。想定内の失敗には、標準ライブラリの runCatching と Result を使えるほか、toIntOrNull() のように例外を投げずに null を返す関数もあります。

fun parse(s: String): Int = try {
    s.toInt()
} catch (e: NumberFormatException) {
    -1
}
fun main() {
    println(parse("42"))              // 42
    println(parse("x"))               // -1
    println("7".toIntOrNull())        // 7
    println("seven".toIntOrNull())    // null
    val r = runCatching { "abc".toInt() }
    println(r.isFailure)              // true
}

小さな完成プログラム:単語の出現頻度

このプログラムは、テキスト中の各単語の出現回数を数え、上位3つを表示します。文字列の拡張、コレクション関数、データクラスを使っています。

data class WordCount(val word: String, val count: Int)
fun topWords(text: String, n: Int): List<WordCount> =
    text.lowercase()
        .split(Regex("[^a-z']+"))
        .filter { it.isNotBlank() }
        .groupingBy { it }
        .eachCount()
        .map { (w, c) -> WordCount(w, c) }
        .sortedWith(compareByDescending<WordCount> { it.count }.thenBy { it.word })
        .take(n)
fun main() {
    val text = "The cat and the dog. The dog sat, and the cat ran."
    for ((word, count) in topWords(text, 3)) {
        println("$word: $count")
    }
}
// the: 4
// and: 2
// cat: 2

次のステップ

  • kotlinlang.orgの公式ドキュメント。まずは「Basic syntax」と「Kotlin tour」から始めましょう。
  • Kotlin Koans。ブラウザ上で実行できる小さな演習集です。
  • AndroidでのKotlinについてはAndroid Developersのガイド、サーバーサイドKotlinについてはKtorやSpringのガイド。
  • 非同期処理が必要になったら、kotlinx.coroutinesのガイド。

よくある質問

KotlinはJavaより優れていますか?

Kotlinはより簡潔に書け、null安全、データクラス、コルーチンといった機能が加わっていますが、同じJVM上で動作し、同じライブラリを使います。1つのプロジェクトで両方の言語を併用するチームも多く、選択の基準は能力の差ではなく、書き心地と生産性です。

Kotlinの前にJavaを学ぶ必要はありますか?

いいえ。最初のJVM言語としてKotlinから学び始められます。ただし、古いAndroidのコードやJavaライブラリのドキュメントを読むときには、Javaの知識が役に立ちます。

valとvarの違いは何ですか?

val は初期化後に再代入できない読み取り専用の参照を宣言し、var は再代入できる参照を宣言します。可変リストを指す val でも、リストの中身は変更できます。

KotlinでiOSアプリは作れますか?

はい。Kotlin Multiplatformを使うと、共有のKotlinコードをiOS向けのネイティブコードにコンパイルできます。UIはSwiftで書くことが多いですが、Compose MultiplatformでUIコードを共有することも可能です。

Kotlinの ?: 演算子は何をしますか?

エルビス演算子です。a ?: b は、a がnullでなければ a を、nullなら b を返します。name?.length ?: 0 のように、セーフコールと組み合わせて使うのが一般的です。

コーディング講座の一覧