Kotlin: Kotlinの変数と型システム

最終更新:2026-08-26

Kotlinの型システムはJavaの単なるコピーではありません。スマートキャストによって強制キャストを排除し、Any/Unit/Nothingを用いて完全な型代数を構築し、型システムが開発者の足を引っ張るのではなく、開発者をサポートするように設計されています。

1. 学習内容


2. ある開発者の実体験

(1) 課題:Javaの型キャスト地獄

Bobが保守していたOrderProcessorのJavaコードには、(Order)(String)といった強制型キャストが散在していました。instanceofチェックを1つ見落とすだけでClassCastExceptionが発生し、四半期ごとに3~5件の運用障害が起きていました。

(2) Kotlinのスマートキャストによる解決策

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 上でそれらをプリミティブ型に最適化します。

KOTLIN
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) 数値型以外の型

KOTLIN
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) ベーシック・スマートキャスト

KOTLIN
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の制限事項

KOTLIN
// 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 は暗黙の型絞り変換(例:LongInt)をサポートしていません。すべての変換は明示的に呼び出す必要があります。

(1) 変換関数

KOTLIN
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) なぜ暗黙の型変換がないのか?

KOTLIN
// 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) 型階層図

100%
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型の親型

KOTLIN
// 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) 単位:型指定された「戻り値なし」

KOTLIN
// 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) なし:到達しない

KOTLIN
// 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)

KOTLIN
// 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

KOTLIN
// ============================================
// 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")
    }
}

出力:

TEXT 📖 参照専用
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

❓ よくある質問

Q KotlinのIntとJavaのintは同じものですか?
A JVM上では、KotlinのIntはJavaのint(プリミティブ型)にコンパイルされ、オブジェクトが必要な場合は自動的にIntegerにオートボックスされます。開発者はこの違いについて気にする必要はありません。
Q スマートキャストはどのような場合に機能しないのですか?
A 変数が var であり、同時に変更される可能性がある場合や、カスタムゲッターを持つ場合、コンパイラはチェック時と使用時の型の一貫性を保証できないため、スマートキャストは適用されません。
Q Nothing 型の実際の用途は何ですか?
A 主に「決して戻らない」(例:例外をスローする)関数をマークするために使用され、コンパイラが後続のコードに到達しないことを理解できるようにすることで、スマートキャストやエルビス演算子の使用を可能にします。
Q typealias は新しい型を作成しますか?
A いいえ。typealias は単なる型エイリアスであり、コンパイラが元の型に置き換えます。 真に型安全なラッパーを使用するには、inline class / value class を使用してください。
Q なぜKotlinは暗黙の型変換をサポートしていないのですか?
A 暗黙の絞り込み変換(例:Long→Int)は、データが失われても警告が出ず、バグの一般的な原因となるためです。明示的な変換を行うことで、開発者は潜在的なデータ損失に気づくことができます。
Q AnyAny? の違いは何ですか?
A Any はすべての非null型の親型であり、Any? はすべての型(null許容型を含む)の親型です。 Any? = Any | nullは、Kotlinの型システムの真の最上位型です。

📖 まとめ


📝 練習問題

  1. 初心者 (⭐): 異なる型の変数 (Int, Double, Boolean, String) を宣言し、それらの型 ::class を出力してください。 ヒント:val x = 42; println(x::class) を使用してください。
  2. 中級 (⭐⭐): Any を受け取る関数を記述し、スマートキャストを使用して String(長さの表示)および Int(二乗値の表示)を処理してください。 ヒント:is String / is Int
  3. 課題 (⭐⭐⭐): typealias とシールクラスを使用して、注文イベント型システムを設計してください。すべてのイベント型は when によって網羅的に処理されるようにしてください。ヒント:第8節の完全な例を参照してください。

← 前へ | 次へ →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%