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ではifとwhenはどちらも値を返す式です。
val max = if (a > b) a else b
val message = when (status) {
"PENDING" -> "保留中"
"CONFIRMED" -> "確定"
in listOf("CANCELLED", "FAILED") -> "失敗"
else -> "不明"
}Javaの三項演算子(? :)がない代わりにif自体がその役割を果たします。whenはswitchよりもずっと柔軟で、範囲チェックや型チェックも可能です。
コレクションの関数型処理#
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