この記事は はてなエンジニア Advent Calendar 2025 16日目の記事です.
昨日は
id:Windymelt さんの HNSWのビジュアライザを作成したでした.
さて,表題の通り最近 Kotlin を書きはじめたので,この記事ではKotlinを書いてみた感想を書いてみようと思います.
ファーストインプレッション
私のバックグラウンドとしては TypeScript をメインにたまにGoやRubyを書くという感じで,いわゆるJVM系言語はほとんど触ったことがありませんでした. 書き始めるまでのKotlinの雑なイメージはこんな感じでした:
書いてみて面白いなと思ったところ
クラスの種類が多い
まずはじめに思ったのはクラスの種類が多いことでしょうか.
TypeScriptではclassだけですが,Kotlinにはdata class,sealed class,enum classなど様々種類のクラスが存在しています.まだ正しい使い分けが理解できていません...
それぞれの役割や特性としては:
data class: データ保持が目的.自動的にデータをコピーできるcopy()メソッドが実装される.- この
copy()メソッドはかなり便利で最初に見たときは感動した覚えがあります.
- この
sealed class: 継承できるクラスを同じパッケージ内に制限する.whenで網羅性チェックができる.enum class: 固定値を列挙できる.各値はインスタンスになる.
when式が強い
when式はswitch文より表現力が高く,分岐が結構簡潔に書けます.
TypeScriptやGoには無い機能なので,チュートリアルを走っている途中には結構強力な構文だなと思っていました
大分雑な例ですが,例えばプログラミングのテストで出てきそうな*1こんな感じのコードが
function formatScore(score: number): string { if (score >= 90) return "秀"; if (score >= 80) return "優"; if (score >= 70) return "良"; if (score >= 60) return "可"; return "不可"; }
Kotlinだとこう書けます:
fun formatScore(score: Int): String = when { score >= 90 -> "秀" score >= 80 -> "優" score >= 70 -> "良" score >= 60 -> "可" else -> "不可" }
named arguments
関数呼び出しの時に引数名を指定できる機能です.
data class User( private val id: String, private val name: String, ) val user = User(id = "20251216", name = "laminne")
これがなかなか便利です. 引数が増えてくると順番を意識して書く必要がありますし,同じ型の引数を取り違えることも良く発生します. 同じような構文はRubyなどにもあります.
同じようなことをTypeScriptで書こうとすると,まずinterfaceやtypeを使ってオブジェクトの型を定義する必要があります:
interface UserArgs { id: string, name: string, } class User { private readonly id: string private readonly name: string constructor(args: UserArgs) { this.id = args.id this.name = args.name } } const user = new User({ id: "20251216", name: "laminne" })
TypeScriptのクラスにはコンストラクタでメンバ変数を定義できる構文があり,上のコードはこんな感じに書き直すことができます
class User { constructor( private readonly id: string, private readonly name: string, ) {} }
とはいえこの方法だとオブジェクトを使って受け取ることができないので,コンストラクタでメンバ変数に代入し直す必要があって辛い...
スコープ関数
スコープ関数はラムダ式を使ってオブジェクトを操作するための関数*2です.
let,run,with,apply,alsoの5種類が存在していて,それぞれ少しずつ違う効果があります.使い分けるのが難しい....
例えばlet: ?.演算子と一緒に使うことが多い,nullableなオブジェクトに対して「中身がnullではない時だけ実行する」処理を書くときに使う.
val email: String? = user.getEmail() val trimmedEmail = email?.let { it.trim() } // 繋げて書くこともできる email ?.let { it.trim() } ?.let { it.lowercase() }
TypeScriptで同じことを書くと
const email: string | null = user.getEmail() const trimmedEmail = email !== null ? email.trim() : null
というようになります. if文や三項演算子で分岐させるのとメソッドチェーンのどっちが良いかは場合によると思っていますが,個人的にはメソッドチェーンできる方がわかりやすくて好きです.
不満点
最近書き始めたばかりで理解していないことの方が多い状態なので,あまり不満はありませんが,強いて言えば:
- ビルドが思ったより重たい
- lintなどの周辺ツールも数秒待つことが多かったです(これは私の設定が悪いかもしれない...)
- 差分ビルドとかをうまく使うともっと早くなるかも
- たまにJavaのメソッドを呼び出して困る
まとめ
- 思ったより便利な構文や機能が多くて簡潔に書けるなという印象を持った
- まだ書き始めてからの時間が経っていないので試せていない機能や理解できていないことが結構ある
- Coroutineは全く理解できていない.使えると便利そう
- Kotlin Nativeが気になっている
- Pure Kotlinでちゃんとしたアプリケーションを作ったり,大きなコードを読んでみようかなと思っている
以上です.
*1:実際に学校のテストでこんな感じのコードをCで書いた覚えがあります