Kotlin文法まとめ

 ・ 5 min

photo by Luca Bravo(https://unsplash.com/@lucabravo?utm_source=templater_proxy&utm_medium=referral) on Unsplash

JavaからKotlinに移行するサービスが増えていますよね。Kotlinを見て最初に感じたのは簡潔さでした。Javaと比べて同じロジックを半分以下のコードで表現できるというのがなかなか新鮮でした。この記事では実務でよく使うKotlin文法を整理してみます。Null Safetyからコルーチンまで、ポイントだけ押さえていきましょう。

変数と型推論#

Kotlinの変数宣言は2種類です。valは不変、varは可変です。

val name: String = "太郎"
val age = 30  // 型推論でInt
 
var count: Int = 0
count = 1
 
const val MAX_SIZE = 100  // コンパイル時定数

Javaで毎回finalを付けていたのと違い、Kotlinではvalがデフォルトなので自然と不変を志向するようになります。

文字列処理もずっとスッキリします。文字列テンプレートとraw stringを使えばStringBuilderを持ち出すことはほとんどありません。

val json = """
    {
        "name": "$name",
        "age": $age
    }
""".trimIndent()

Null Safety#

Kotlinを使う最大の理由の一つです。すべての型がデフォルトでnon-nullなので、NullPointerExceptionをコンパイル時に検出できます。

var nickname: String? = null  // ?を付けるとnull許容
 
nickname?.length              // Safe call: nullならnullを返す
nickname!!.length             // Non-null assertion: nullならクラッシュ
val len = nickname?.length ?: 0  // Elvis演算子: nullのときデフォルト値

実務ではletとElvis演算子を組み合わせるパターンが最もよく使われます。

nickname?.let {
    println("ニックネーム: $it")
}
 
fun process(name: String?) {
    val n = name ?: return  // nullなら早期リターン
    println(n.uppercase())
}

!!演算子はテストコード以外では使わない方がいいです。結局NPEを再び生み出すのと同じですから。

関数と拡張関数#

デフォルト値と名前付き引数のおかげで、Javaでオーバーロードで解決していた問題が関数一つで済みます。

fun greet(name: String = "Guest", age: Int = 0) = "Hello, $name (${age}歳)"
 
greet(age = 30, name = "太郎")  // 順序を無視できる

拡張関数は既存クラスを変更せずにメソッドを追加する機能です。ユーティリティ関数を作るときに特に便利です。

fun String.toSlug() =
    this.lowercase().replace(" ", "-").replace(Regex("[^a-z0-9-]"), "")
 
"Hello World".toSlug()  // "hello-world"

クラス:Data ClassとSealed Class#

Data Class#

DTOやモデルオブジェクトを作るとき、equals()hashCode()toString()copy()を自動生成してくれます。Javaのボイラープレートを完全になくせます。

data class User(
    val id: Long,
    val name: String,
    val email: String
)
 
val user2 = user1.copy(name = "次郎")  // 一部フィールドだけ変更したコピー

Sealed Class#

状態管理に特化したクラスです。when式と組み合わせると、すべてのケースをコンパイラがチェックしてくれるためelse分岐が不要になります。

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String) : Result<Nothing>()
    object Loading : Result<Nothing>()
}
 
fun handleResult(result: Result<String>) = when (result) {
    is Result.Success -> "成功: ${result.data}"
    is Result.Error -> "エラー: ${result.message}"
    Result.Loading -> "読み込み中..."
}

新しい状態を追加するとwhenを使っているすべての箇所でコンパイルエラーが発生するので、漏れなく対応できます。

制御文は式である#

Kotlinではifwhenはどちらも値を返す式です。

val max = if (a > b) a else b
 
val message = when (status) {
    "PENDING" -> "保留中"
    "CONFIRMED" -> "確定"
    in listOf("CANCELLED", "FAILED") -> "失敗"
    else -> "不明"
}

Javaの三項演算子(? :)がない代わりにif自体がその役割を果たします。whenswitchよりもずっと柔軟で、範囲チェックや型チェックも可能です。

コレクションの関数型処理#

KotlinのコレクションAPIはJavaのStreamよりも簡潔です。

val numbers = listOf(1, 2, 3, 4, 5)
 
numbers.filter { it > 2 }           // [3, 4, 5]
numbers.map { it * 2 }              // [2, 4, 6, 8, 10]
numbers.groupBy { it % 2 == 0 }     // {false=[1,3,5], true=[2,4]}
numbers.chunked(2)                  // [[1,2], [3,4], [5]]

.stream().collect(Collectors.toList())のようなコードを書かなくて済みます。

スコープ関数#

5つのスコープ関数は最初は混乱しますが、ポイントは参照方法it vs this)と戻り値(ラムダの結果 vs オブジェクト自体)の組み合わせです。

関数 参照 戻り値 用途
let it ラムダ結果 nullチェック、変換
run this ラムダ結果 設定後に計算
with this ラムダ結果 複数メソッド呼び出し
apply this オブジェクト自体 オブジェクト初期化
also it オブジェクト自体 ログ、付随処理

実務で最もよく使う組み合わせはapply + alsoです。

val user = User().apply {
    name = "太郎"
    age = 30
}.also {
    log("ユーザー作成: $it")
}

applyでオブジェクトを初期化し、alsoでサイドエフェクト(ログなど)を処理するパターンです。

コルーチン#

Kotlinの非同期処理はコルーチンが担当します。RxJavaより学習コストが低く、コードが同期的に読めます。

基本的なAPI呼び出し#

viewModelScope.launch {
    _loading.value = true
    _user.value = repository.getUser(id)
    _loading.value = false
}

並列呼び出し#

逐次実行なら3秒かかる処理を、asyncで並列処理すれば1秒に短縮できます。

coroutineScope {
    val user = async { fetchUser() }
    val posts = async { fetchPosts() }
    val comments = async { fetchComments() }
    Triple(user.await(), posts.await(), comments.await())
}

Dispatcherでスレッド切り替え#

viewModelScope.launch {
    showLoading()  // Mainスレッド
 
    val user = withContext(Dispatchers.IO) {
        api.getUser(id)  // IOスレッド
    }
 
    showUser(user)  // 再びMainスレッド
}

withContextでスレッドを切り替えてもコールバック地獄なしに上から下へ読めます。

Java相互運用#

既存のJavaコードとの互換が必要なら、@JvmStatic@JvmFieldなどのアノテーションを活用すれば大丈夫です。

class Utils {
    companion object {
        @JvmStatic
        fun helper() { }
    }
}
// JavaからUtils.helper()で呼び出し可能

まとめ#

Kotlinは結局安全で簡潔なコードを目指す言語です。この記事で取り上げた中で、実務で最も実感が大きかったのは次の3つでした。

  • Null Safety - ランタイムクラッシュをコンパイル時に引き上げてくれます
  • Sealed Class + when - 状態の漏れがない分岐処理が可能です
  • コルーチン - コールバックなしで非同期コードを書けます

もっと深く学びたい方はKotlin Koansで練習してみてください。


We learn what we have said from those who listen to our speaking.

— Kenneth Patton


他の投稿
メモは積むものではなくつなげるもの 커버 이미지
 ・ 1 min

メモは積むものではなくつなげるもの

意志力は筋肉のようなもの 커버 이미지
 ・ 1 min

意志力は筋肉のようなもの

良いワークフローの好循環 커버 이미지
 ・ 1 min

良いワークフローの好循環