Kotlin: Kotlinの変数と型システム
最終更新:2026-08-26
Kotlinの型システムはJavaの単なるコピーではありません。スマートキャストによって強制キャストを排除し、Any/Unit/Nothingを用いて完全な型代数を構築し、型システムが開発者の足を引っ張るのではなく、開発者をサポートするように設計されています。
1. 学習内容
- 基本タイプ:
Int/Double/Boolean/Char/String - スマートキャスト:
isの確認後、すぐに使用してください - 明示的な型変換:
toInt()/toDouble()、暗黙的な型縮小なし Any/Unit/Nothing— 3つの最上位型- 可読性を高めるため、
typealiasのエイリアスを定義する
2. ある開発者の実体験
(1) 課題:Javaの型キャスト地獄
Bobが保守していたOrderProcessorのJavaコードには、(Order)や(String)といった強制型キャストが散在していました。instanceofチェックを1つ見落とすだけでClassCastExceptionが発生し、四半期ごとに3~5件の運用障害が起きていました。
(2) Kotlinのスマートキャストによる解決策
// Java: verbose and error-prone
if (obj instanceof Order) {
Order order = (Order) obj; // Must cast again
System.out.println(order.getId());
}
// Kotlin: smart cast eliminates the cast
if (obj is Order) {
println(obj.id) // No cast needed!
}
Kotlinコンパイラは、
isによるチェックの後に安全な型変換を自動挿入します。それ以来、Bobは手動の型変換を書いていません。
3. 基本型
(1) 数値型
Kotlin では Java のプリミティブ型は公開されていません(int/double)— すべてがオブジェクトとして扱われます — ただし、コンパイラが JVM 上でそれらをプリミティブ型に最適化します。
val intValue: Int = 42 // 32-bit
val longValue: Long = 42L // 64-bit
val doubleValue: Double = 3.14 // 64-bit IEEE 754
val floatValue: Float = 3.14f // 32-bit IEEE 754
// Underscore for readability
val million = 1_000_000
val creditCard = 1234_5678_9012_3456L
val bytes = 0b11010010_01101001
| 型 | ビット数 | 範囲 | Javaでの対応 | |
|:-----|:-----|:------|:----------|
| Byte | 8 | -128 ~ 127 | byte |
| Short | 16 | -32768 ~ 32767 | short |
| Int | 32 | -2³¹ ~ 2³¹-1 | int |
| Long | 64 | -2⁶³ ~ 2⁶³-1 | long |
| Float | 32 | IEEE 754 | float |
| Double | 64 | IEEE 754 | double |
(2) 数値型以外の型
val bool: Boolean = true
val char: Char = 'A'
val str: String = "OrderProcessor"
// Character is NOT a number in Kotlin
// val code: Int = 'A' // ERROR
val code: Int = 'A'.code // OK: 65
val charFromCode: Char = 65.toChar() // OK: 'A'
4. スマートキャスト
Kotlin コンパイラは、型チェックの後、変数をより具体的な型に自動的にキャストします。手動でのキャストは不要です。
(1) ベーシック・スマートキャスト
fun process(input: Any) {
// After 'is' check, compiler smart casts
if (input is String) {
println(input.length) // Smart cast to String
}
// After '!is' check
if (input !is String) return
println(input.length) // Smart cast to String
}
// Smart cast with when
fun describe(obj: Any): String = when (obj) {
is String -> "String: ${obj.length} chars" // Smart cast
is Int -> "Int: ${obj.dec()}" // Smart cast
is List<*> -> "List: ${obj.size} items" // Smart cast
else -> "Unknown"
}
(2) Smart Castの制限事項
// WARNING: smart cast may not work with var or custom getters
var obj: Any = "Hello"
if (obj is String) {
// obj could be changed between check and use
// println(obj.length) // WARNING in some cases
}
// Safe approach: use local val
val safeObj = obj
if (safeObj is String) {
println(safeObj.length) // OK - guaranteed stable
}
(3) JavaのキャストとKotlinのスマートキャストの比較
| 次元 | Java | Kotlin |
|---|---|---|
| 構文 | (Type)obj |
自動 |
| 安全性 | 実行時にCCEを引き起こす可能性がある | コンパイル時の保証 |
| コードサイズ | チェック+型変換(2ステップ) | チェック=型変換(1ステップ) |
| null安全性 | NPE が発生する可能性がある | null可能な値を自動的に処理 |
5. 明示的な型変換
Kotlin は暗黙の型絞り変換(例:Long → Int)をサポートしていません。すべての変換は明示的に呼び出す必要があります。
(1) 変換関数
val longVal: Long = 42L
val intVal: Int = longVal.toInt() // Explicit narrowing
val doubleVal: Double = 3.99
val intFromDouble: Int = doubleVal.toInt() // Truncates: 3
// String to number
val parsed: Int = "42".toInt()
val parsedDouble: Double = "3.14".toDouble()
// Number to string
val strVal: String = 42.toString()
(2) なぜ暗黙の型変換がないのか?
// This would silently lose data - Kotlin prevents it
val longValue: Long = 2_147_483_648L // Exceeds Int.MAX_VALUE
// val intValue: Int = longValue // COMPILE ERROR
// Must be explicit about potential data loss
val intValue: Int = longValue.toInt() // OK but may overflow
| 変換タイプ | Java | Kotlin |
|---|---|---|
| 拡張(Int→Long) | 暗黙的 | 明示的 toLong() |
| 絞り込み(Long→Int) | 暗黙的(リスクあり) | 明示的 toInt() |
| 文字列→数値 | Integer.parseInt() |
"42".toInt() |
| 数値→文字列 | "" + 42 |
42.toString() |
6. 3つの最上位型
(1) 型階層図
classDiagram
Any --> String
Any --> Int
Any --> Order
Any --> Unit
Any --> Nothing
class Any {
+equals()
+hashCode()
+toString()
}
class Unit {
<<singleton>>
}
class Nothing {
<<never returns>>
}
(2) Any:すべての非Null型の親型
// Any is the root of the Kotlin type hierarchy (like Object in Java)
val obj: Any = "Hello"
val obj2: Any = 42
val obj3: Any = Order("ORD-001", 299.99, "CONFIRMED")
// Any has only 3 methods: equals, hashCode, toString
// Use Any? for nullable root type
(3) 単位:型指定された「戻り値なし」
// Unit is like void in Java, but it's a real type
fun logOrder(order: Order): Unit { // Unit return is optional
println("Processing ${order.id}")
}
fun logOrder2(order: Order) { // Equivalent - Unit inferred
println("Processing ${order.id}")
}
// Unit is a singleton - can be used as a value
val unitValue: Unit = Unit
(4) なし:到達しない
// Nothing means "this code never returns"
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
// Useful for Elvis operator with exceptions
val customer = order.customer ?: fail("Customer required")
// Infinite loop also returns Nothing
fun infiniteLoop(): Nothing {
while (true) { /* never returns */ }
}
(5) 3つのトップレベル型の比較
| タイプ | 意味 | 使用例 | Javaでの対応 | |
|:-----|:-----|:---------|:----------|
| Any | すべての非null型の親 | ジェネリック参照 | Object |
| Unit | 意味のある戻り値がない | 副作用のある関数 | void |
| Nothing | 終了しない | 例外の発生/無限ループ | なし |
7. 型エイリアス (typealias)
// Simplify complex type signatures
typealias OrderMap = Map<String, List<Order>>
typealias OrderProcessor = (List<Order>) -> List<String>
typealias USD = Double
// Usage
val ordersByCustomer: OrderMap = mapOf(
"Alice" to listOf(Order("ORD-001", 299.99, "CONFIRMED"))
)
typealias Predicate<T> = (T) -> Boolean
val isHighValue: Predicate<Order> = { it.total > 10_000 }
// typealias does NOT create new types - just aliases
val map: OrderMap = ordersByCustomer // Same type
8. 完全な例:OrderProcessor による型安全な処理
▶ サンプル:型安全なOrderProcessor
// ============================================
// OrderProcessor - Type-Safe Event Processing
// Feature: Process order events with smart cast
// ============================================
typealias OrderId = String
typealias USD = Double
sealed class OrderEvent {
data class Created(val orderId: OrderId, val total: USD) : OrderEvent()
data class Paid(val orderId: OrderId, val amount: USD) : OrderEvent()
data class Shipped(val orderId: OrderId, val trackingCode: String) : OrderEvent()
data class Cancelled(val orderId: OrderId, val reason: String) : OrderEvent()
}
fun handleEvent(event: OrderEvent): String = when (event) {
is OrderEvent.Created -> {
// Smart cast: event.orderId and event.total available
val threshold = 10_000
val priority = if (event.total > threshold) "VIP" else "STANDARD"
"Order ${event.orderId} created (\$${event.total} USD) -> $priority"
}
is OrderEvent.Paid -> {
val tax = event.amount * 0.08
"Order ${event.orderId} paid: \$${event.amount} USD (tax: \$$tax USD)"
}
is OrderEvent.Shipped ->
"Order ${event.orderId} shipped: ${event.trackingCode}"
is OrderEvent.Cancelled ->
"Order ${event.orderId} cancelled: ${event.reason}"
}
fun main() {
val events: List<OrderEvent> = listOf(
OrderEvent.Created("ORD-001", 299.99),
OrderEvent.Created("ORD-002", 15_000.00),
OrderEvent.Paid("ORD-001", 299.99),
OrderEvent.Shipped("ORD-001", "TRK-ABC123"),
OrderEvent.Cancelled("ORD-003", "Customer request")
)
events.forEach { event ->
val result = handleEvent(event)
println(result)
}
// Type check with smart cast
val anyEvent: Any = events[0]
if (anyEvent is OrderEvent.Created) {
println("\nSmart cast works: ${anyEvent.orderId} costs \$${anyEvent.total} USD")
}
}
出力:
Order ORD-001 created ($299.99 USD) -> STANDARD
Order ORD-002 created ($15000.0 USD) -> VIP
Order ORD-001 paid: $299.99 USD (tax: $23.999200000000002 USD)
Order ORD-001 shipped: TRK-ABC123
Order ORD-003 cancelled: Customer request
Smart cast works: ORD-001 costs $299.99 USD
❓ よくある質問
IntとJavaのintは同じものですか?IntはJavaのint(プリミティブ型)にコンパイルされ、オブジェクトが必要な場合は自動的にIntegerにオートボックスされます。開発者はこの違いについて気にする必要はありません。var であり、同時に変更される可能性がある場合や、カスタムゲッターを持つ場合、コンパイラはチェック時と使用時の型の一貫性を保証できないため、スマートキャストは適用されません。Nothing 型の実際の用途は何ですか?typealias は新しい型を作成しますか?typealias は単なる型エイリアスであり、コンパイラが元の型に置き換えます。 真に型安全なラッパーを使用するには、inline class / value class を使用してください。Long→Int)は、データが失われても警告が出ず、バグの一般的な原因となるためです。明示的な変換を行うことで、開発者は潜在的なデータ損失に気づくことができます。Any と Any? の違いは何ですか?Any はすべての非null型の親型であり、Any? はすべての型(null許容型を含む)の親型です。 Any? = Any | nullは、Kotlinの型システムの真の最上位型です。📖 まとめ
- Kotlinの基本型はコード上ではオブジェクトですが、コンパイラによってJVM上ではプリミティブ型に最適化されます
isによるチェックの後、Smart Castが自動的にキャストを行うため、Javaで強制される型変換が不要になります- すべての型変換は明示的に行う必要があります(
toInt()/toDouble())。暗黙の型縮小は行わないでください。 Anyは null ではない型の基底型であり、Unitは void の型付きバージョンであり、Nothingは決して戻り値を返さないことを意味するtypealiasは、新しい型を作成することなく、複雑な型に対して読みやすいエイリアスを提供します- 数値にアンダースコア
1_000_000を挿入すると、大きな数字の可読性が向上します
📝 練習問題
- 初心者 (⭐): 異なる型の変数 (
Int,Double,Boolean,String) を宣言し、それらの型::classを出力してください。 ヒント:val x = 42; println(x::class)を使用してください。 - 中級 (⭐⭐):
Anyを受け取る関数を記述し、スマートキャストを使用してString(長さの表示)およびInt(二乗値の表示)を処理してください。 ヒント:is String/is Int - 課題 (⭐⭐⭐):
typealiasとシールクラスを使用して、注文イベント型システムを設計してください。すべてのイベント型はwhenによって網羅的に処理されるようにしてください。ヒント:第8節の完全な例を参照してください。