Kotlin: Kotlinの基本構文
最終更新:2026-08-26
Kotlinの構文哲学は、可能な限りステートメントの代わりに式を使用し、不要な行は決して書かないというものです。これにより、AliceはJavaよりも40%短いコードで、より多くのことを表現できるようになります。
1. 学習内容
val対var:不変性を最優先とする考え方- 文字列テンプレートと生文字列
whenという表現のあらゆる形態- 範囲と反復回数:
../until/step/downTo - 式本体関数とイディオムの慣例
2. ある開発者の実体験
(1) 課題:可変状態が並行処理のバグを引き起こす
Aliceは、OrderProcessor内で共有注文カウンターを定義するためにvarを使用しました。高い同時実行率の下では、データ競合により2%の集計誤差が発生しました。これは、大規模な運用において20,000件の注文が誤って集計されたことに相当します。
(2) 不変性を最優先とする解決策
KOTLIN
// BAD: mutable shared state
var orderCount = 0 // Race condition!
// GOOD: immutable + functional pipeline
val processedOrders = orders
.filter { it.status == "CONFIRMED" }
.also { println("Processed ${it.size} orders") }
不変データは本質的にスレッドセーフです。
valはすべてのKotlin開発者にとって第一の選択肢であり、varは最後の手段です。
3. val 対 var:不変性を最優先
(1) 基本的な構文
KOTLIN
// val: read-only (immutable reference) - PREFERRED
val orderId = "ORD-001" // Type inferred as String
val total: Double = 299.99 // Explicit type
// var: mutable (reassignable) - USE SPARINGLY
var status = "PENDING" // Can be reassigned
status = "CONFIRMED" // OK
// val does NOT mean the object is immutable
val items = mutableListOf<Order>() // Reference is val, list is mutable
items.add(Order("ORD-002", 150.0, "NEW")) // OK - modifying the list
// items = mutableListOf() // ERROR - cannot reassign val
(2) val と var の比較
| 寸法 | val |
var |
|---|---|---|
| 再割り当て可能 | いいえ | はい |
| スレッドセーフ | 参照は不変であり、本質的に安全 | 同期化が必要 |
| 推奨 | 優先 | 最終手段 |
| Javaの対応 | final |
通常の変数 |
| 内部構造 | 読み取り専用参照(オブジェクトは変更可能である場合がある) | 変更可能な参照 |
4. 文字列テンプレート
(1) 基本テンプレート
KOTLIN
val orderId = "ORD-001"
val total = 299.99
// Simple variable reference
println("Order: $orderId")
// Expression in template
println("Total: \$$total USD")
println("Tax: \$${total * 0.08} USD")
// Arbitrary expression with ${}
val items = listOf("Widget", "Gadget")
println("Item count: ${items.size}, First: ${items[0]}")
(2) 生の文字列(複数行)
KOTLIN
val receipt = """
|Order ID: ORD-001
|Total: $299.99 USD
|Status: CONFIRMED
""".trimMargin()
// Alternative: trimIndent
val json = """
{
"order_id": "ORD-001",
"total": 299.99
}
""".trimIndent()
(3) 文字列テンプレートの比較
| 特集 | Java | Kotlin |
|---|---|---|
| 変数参照 | "Order " + id |
"Order $id" |
| 式 | "Tax: " + (t * 0.08) |
"Tax: ${t * 0.08}" |
| 複数行 | ネイティブサポートなし | """ ... """ |
| インデントの扱い | 手動 | trimMargin() / trimIndent() |
5. 「when」式
when は switch の全面的なアップグレード版であり、型、範囲、および任意の条件に一致する戻り値を持つ式です。
(1) 基本的な使い方
KOTLIN
// when as expression (must be exhaustive)
val status = "CONFIRMED"
val label = when (status) {
"PENDING" -> "Waiting for payment"
"CONFIRMED" -> "Payment received"
"SHIPPED" -> "In transit"
"CANCELLED" -> "Order cancelled"
else -> "Unknown status"
}
(2) 高度なマッチング
KOTLIN
// Multiple values
when (status) {
"PENDING", "NEW" -> processNewOrder()
in listOf("CONFIRMED", "PAID") -> shipOrder()
else -> handleOther()
}
// Range matching
val quantity = 150
when (quantity) {
in 1..10 -> println("Small order")
in 11..100 -> println("Medium order")
in 101..1_000 -> println("Large order")
else -> println("Bulk order")
}
// Type matching
fun describe(obj: Any): String = when (obj) {
is String -> "String of length ${obj.length}"
is Int -> "Integer: $obj"
is List<*> -> "List with ${obj.size} elements"
else -> "Unknown type"
}
(3) 決定木の場合
flowchart LR
A[when status] --> B["PENDING, NEW"]
A --> C["CONFIRMED, PAID"]
A --> D["SHIPPED"]
A --> E["CANCELLED"]
A --> F[else]
B --> B1[processNewOrder]
C --> C1[shipOrder]
D --> D1[trackShipment]
E --> E1[refundOrder]
F --> F1[handleOther]
(4) when と switch の違い
| 次元 | Javaのswitch | Kotlinの場合 |
|---|---|---|
| 戻り値 | 文(戻り値なし) | 式(値を返す) |
| 複数値の一致 | フォールスルーが必要 | "A", "B" カンマ区切り |
| 範囲一致 | 利用不可 | in 1..100 |
| タイプ一致 | 利用不可 | is String |
| ガード条件 | 利用不可 | x > 0 任意 |
| 完全性チェック | なし | コンパイラによる強制 (式モード) |
6. 範囲と反復処理
(1) 範囲演算子
KOTLIN
// Closed range: includes both ends
for (i in 1..5) print("$i ") // 1 2 3 4 5
// Half-open range: excludes end
for (i in 1 until 5) print("$i ") // 1 2 3 4
// Step
for (i in 1..10 step 2) print("$i ") // 1 3 5 7 9
// DownTo
for (i in 5 downTo 1) print("$i ") // 5 4 3 2 1
// Char range
for (c in 'A'..'E') print("$c ") // A B C D E
(2) コレクションの反復処理
KOTLIN
val orders = listOf(
Order("ORD-001", 299.99),
Order("ORD-002", 1_500.00),
Order("ORD-003", 45.50)
)
// Iterate with index
for ((index, order) in orders.withIndex()) {
println("[$index] ${order.id}: \$${order.total} USD")
}
// Iterate map
val statusMap = mapOf("ORD-001" to "SHIPPED", "ORD-002" to "PENDING")
for ((id, status) in statusMap) {
println("$id -> $status")
}
(3) 範囲演算子のクイックリファレンス
| 演算子 | 意味 | 例 | 結果 |
|---|---|---|---|
.. |
閉区間 | 1..5 |
1,2,3,4,5 |
until |
半開区間 | 1 until 5 |
1,2,3,4 |
step |
ステップサイズ | 1..10 step 3 |
1,4,7,10 |
downTo |
減少中 | 5 downTo 1 |
5,4,3,2,1 |
in |
メンバーシップの確認 | 3 in 1..5 |
true |
7. 式本体関数とイディオム
(1) 式本体関数
KOTLIN
// Block body
fun calculateTax(total: Double): Double {
return total * 0.08
}
// Expression body (more concise)
fun calculateTax(total: Double): Double = total * 0.08
// Type inference with expression body
fun calculateTax(total: Double) = total * 0.08
(2) Kotlinのイディオム
KOTLIN
// 1. Semicolons are optional
val x = 1 // No semicolon needed
// 2. Trailing lambda convention
orders.filter { it.total > 1_000 } // lambda outside parens
// 3. It keyword for single-parameter lambda
orders.map { it.id } // 'it' = implicit parameter
// 4. String template over concatenation
val msg = "Order $orderId confirmed" // Not "Order " + orderId + " confirmed"
// 5. Expression body for simple functions
fun isHighValue(order: Order) = order.total > 10_000
(3) JavaとKotlinのイディオムの比較
| イディオム | Java | Kotlin |
|---|---|---|
| セミコロン | 必須 | 任意 |
| 文字列連結 | + 演算子 |
$ テンプレート |
| 単純な関数 | ブロック本体 | 式本体 |
| ラムダの配置 | 括弧の内側 | 括弧の直後 |
| 単一パラメータのラムダ | 名前付きパラメータ | it キーワード |
8. 完全な例:OrderProcessor による注文のルーティング
KOTLIN
// ============================================
// OrderProcessor - Order Routing Engine
// Feature: Route orders based on status and value
// ============================================
data class Order(val id: String, val total: Double, val status: String)
fun routeOrder(order: Order): String = when {
order.status == "CANCELLED" -> "REFUND_QUEUE"
order.total > 10_000 -> "VIP_PIPELINE"
order.total > 1_000 -> "PRIORITY_PIPELINE"
order.status == "CONFIRMED" -> "STANDARD_PIPELINE"
order.status in listOf("PENDING", "NEW") -> "WAITING_QUEUE"
else -> "MANUAL_REVIEW"
}
fun processBatch(orders: List<Order>) {
for ((index, order) in orders.withIndex()) {
val pipeline = routeOrder(order)
val priority = when (pipeline) {
"VIP_PIPELINE" -> "CRITICAL"
"PRIORITY_PIPELINE" -> "HIGH"
"STANDARD_PIPELINE" -> "NORMAL"
else -> "LOW"
}
println("[${index + 1}] ${order.id} -> $pipeline (Priority: $priority)")
}
}
fun main() {
val orders = listOf(
Order("ORD-001", 299.99, "CONFIRMED"),
Order("ORD-002", 15_000.00, "CONFIRMED"),
Order("ORD-003", 2_500.00, "PENDING"),
Order("ORD-004", 45.50, "CANCELLED"),
Order("ORD-005", 750.00, "NEW")
)
println("=== Order Routing Report ===")
processBatch(orders)
// Summary with functional pipeline
val pipelineCounts = orders
.map { routeOrder(it) }
.groupingBy { it }
.eachCount()
println("\n=== Pipeline Distribution ===")
for ((pipeline, count) in pipelineCounts) {
println(" $pipeline: $count orders")
}
}
出力:
TEXT
📖 参照専用
=== Order Routing Report ===
[1] ORD-001 -> STANDARD_PIPELINE (Priority: NORMAL)
[2] ORD-002 -> VIP_PIPELINE (Priority: CRITICAL)
[3] ORD-003 -> PRIORITY_PIPELINE (Priority: HIGH)
[4] ORD-004 -> REFUND_QUEUE (Priority: LOW)
[5] ORD-005 -> WAITING_QUEUE (Priority: LOW)
=== Pipeline Distribution ===
STANDARD_PIPELINE: 1 orders
VIP_PIPELINE: 1 orders
PRIORITY_PIPELINE: 1 orders
REFUND_QUEUE: 1 orders
WAITING_QUEUE: 1 orders
❓ よくある質問
Q val コレクションの内容を変更することはできますか?
A はい。val は参照が不変であることを保証するのみであり、コレクションの内容自体は変更可能です。真に不変のコレクションが必要な場合は、
mutableListOf() の代わりに listOf() を使用してください。Q
when には必ず else が必要ですか?A
when when`` 式として使用する場合、コンパイラは網羅性を要求します。つまり、すべての分岐を網羅するか、else を追加する必要があります。文として使用する場合、else は省略可能です。Q
1..5 と 1 until 6 は同じものですか?A 結果は同じですが、意味合いが異なります。
1..5は「1から5まで(5を含む)」を意味し、1 until 6は「1から6まで(6を含まない)」を意味します。リストのインデックスを反復処理する際は、untilの方が一般的です。Q 式本体関数はいつ使うべきですか?
A 関数が単一の式である場合は、式本体を使用してください。2行以上のロジックを含む場合は、ブロック本体を使用してください。可読性を最優先にしてください。
Q 文字列テンプレートにおける
$var と ${expr} の違いは何ですか?A
$var では単純な変数名のみを参照できますが、 ${expr}には、${items.size}や${total * 0.08}と同様に、任意の式を含めることができます。Q
when 構文で浮動小数点の範囲を指定することはできますか?A 技術的には可能ですが、浮動小数点の精度の問題により予期しない結果が生じる可能性があります。整数の範囲を使用するか、明示的な比較を行うことをお勧めします。
▶ サンプル:val 対 var
KOTLIN
val name = "Alice" // immutable
var age = 30 // mutable
age = 31 // OK
// name = "Bob" // compile error
出力:
TEXT
📖 参照専用
Compiles; reassigning name fails
▶ サンプル:Expression の場合
KOTLIN
val score = 85
val grade = when {
score >= 90 -> "A"
score >= 80 -> "B"
else -> "C"
}
println(grade)
出力:
TEXT
📖 参照専用
B
▶ サンプル:forループ
KOTLIN
for (i in 1..3) println(i)
for (s in listOf("a", "b")) println(s)
出力:
TEXT
📖 参照専用
1
2
3
a
b
📖 まとめ
valが先頭、varが末尾 — 不変参照は本質的にスレッドセーフである$/${}を使用した文字列テンプレートは、文字列の連結に代わるものです。"""は複数行用です。whenはswitchの機能強化版であり、多値、範囲、型、およびガードマッチングに対応しています。- 範囲演算子:
..(閉)、until(半開)、step、downTo - 式本体関数を使えば、単純な関数を簡潔に記述できます。読みやすさを基準に使い分けます。
- Kotlinのイディオム:セミコロンなし、末尾のラムダ式、
itキーワード、文字列テンプレート
📝 練習問題
- 初心者 (⭐):
valと文字列テンプレートを使用して、注文の概要"ORD-001: $299.99 USD (CONFIRMED)"を出力する関数を作成してください。ヒント:val id = "ORD-001"、val total = 299.99 - 中級 (⭐⭐):
whenを使用した割引計算機を実装してください。条件:10,000 以上 → 20% オフ、5,000 以上 → 10% オフ、それ以外は定価。 ヒント:when {}ガード条件 - 課題 (⭐⭐⭐):範囲と
whenを使用して、累進課税の計算ツールを実装してください。所得区分ごとに異なる税率が適用されます。ヒント:when { income in range -> ... }