メインコンテンツにスキップ

⚠️ 例外処理

📖 例外とは?

**例外(Exception)**は、プログラム実行中に発生するエラーです。例外を適切に処理すれば、プログラムが停止せず安定的に動作します!

💡 基本的なtry-catch

基本的な使い方

fun main() {
try {
val result = 10 / 0 // エラー発生!
println(result)
} catch (e: Exception) {
println("エラー発生: ${e.message}")
}
// エラー発生: / by zero

println("プログラム継続実行!")
}

特定の例外処理

fun main() {
try {
val text = "abc"
val number = text.toInt() // NumberFormatException
} catch (e: NumberFormatException) {
println("数値に変換できません: ${e.message}")
} catch (e: Exception) {
println("不明なエラー: ${e.message}")
}
}

finally

fun main() {
try {
println("作業開始")
// 作業実行
} catch (e: Exception) {
println("エラー発生")
} finally {
println("クリーンアップ作業 (常に実行)")
}
}

🎯 実践例

安全な数値変換

fun safeToInt(text: String): Int? {
return try {
text.toInt()
} catch (e: NumberFormatException) {
null
}
}

fun main() {
println(safeToInt("123")) // 123
println(safeToInt("abc")) // null

val age = safeToInt("25") ?: 0
println("年齢: $age")
}

ファイル読み取りシミュレーション

class FileReader {
fun readFile(filename: String): String {
if (filename.isEmpty()) {
throw IllegalArgumentException("ファイル名が空です")
}
if (!filename.endsWith(".txt")) {
throw IllegalArgumentException("テキストファイルのみサポートされています")
}
return "ファイル内容: $filename"
}
}

fun main() {
val reader = FileReader()

try {
val content = reader.readFile("data.txt")
println(content)
} catch (e: IllegalArgumentException) {
println("エラー: ${e.message}")
}

try {
reader.readFile("")
} catch (e: IllegalArgumentException) {
println("エラー: ${e.message}")
// エラー: ファイル名が空です
}
}

ユーザー入力検証

data class User(val name: String, val age: Int)

fun createUser(name: String, age: Int): User {
require(name.isNotBlank()) { "名前は必須です" }
require(age > 0) { "年齢は正の数である必要があります" }
require(age < 150) { "年齢が大きすぎます" }

return User(name, age)
}

fun main() {
try {
val user1 = createUser("田中太郎", 25)
println(user1)

val user2 = createUser("", 25) // エラー!
} catch (e: IllegalArgumentException) {
println("検証失敗: ${e.message}")
// 検証失敗: 名前は必須です
}
}

APIレスポンス処理

sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}

fun divide(a: Int, b: Int): Result<Int> {
return try {
Result.Success(a / b)
} catch (e: ArithmeticException) {
Result.Error(e)
}
}

fun main() {
val result1 = divide(10, 2)
when (result1) {
is Result.Success -> println("結果: ${result1.data}")
is Result.Error -> println("エラー: ${result1.exception.message}")
}

val result2 = divide(10, 0)
when (result2) {
is Result.Success -> println("結果: ${result2.data}")
is Result.Error -> println("エラー: ${result2.exception.message}")
}
}

🔍 式として使用

tryを値として

fun main() {
val number = try {
"123".toInt()
} catch (e: NumberFormatException) {
0 // デフォルト値
}

println(number) // 123

val invalid = try {
"abc".toInt()
} catch (e: NumberFormatException) {
-1
}

println(invalid) // -1
}

チェーン

fun safeDivide(a: Int, b: Int): Int? {
return try {
a / b
} catch (e: ArithmeticException) {
null
}
}

fun main() {
val result = safeDivide(10, 2)
?.let { it * 2 }
?.let { it + 5 }
?: 0

println(result) // 15 (10/2 * 2 + 5)
}

🛡️ 安全なコード作成

require vs check

fun processOrder(quantity: Int, price: Int) {
// 入力検証
require(quantity > 0) { "数量は正の数である必要があります" }
require(price > 0) { "価格は正の数である必要があります" }

val total = quantity * price

// 状態検証
check(total < 1000000) { "合計金額が大きすぎます" }

println("注文完了: ${total}円")
}

fun main() {
try {
processOrder(10, 5000) // 成功
processOrder(-1, 5000) // require失敗
} catch (e: IllegalArgumentException) {
println("入力エラー: ${e.message}")
} catch (e: IllegalStateException) {
println("状態エラー: ${e.message}")
}
}

checkNotNull

fun processName(name: String?) {
val validName = checkNotNull(name) { "名前がnullです" }
println("処理: $validName")
}

fun main() {
try {
processName("田中太郎") // 成功
processName(null) // 失敗
} catch (e: IllegalStateException) {
println("エラー: ${e.message}")
}
}

runCatching

Kotlin 1.3から提供される便利な方法です!

fun main() {
// runCatching使用
val result = runCatching {
"123".toInt()
}

result
.onSuccess { println("成功: $it") }
.onFailure { println("失敗: ${it.message}") }

// getOrNull
val number = runCatching { "abc".toInt() }.getOrNull()
println(number) // null

// getOrDefault
val safe = runCatching { "abc".toInt() }.getOrDefault(0)
println(safe) // 0
}

🎯 実用パターン

リトライロジック

fun <T> retry(times: Int, block: () -> T): T? {
repeat(times) { attempt ->
try {
return block()
} catch (e: Exception) {
println("試行 ${attempt + 1} 失敗: ${e.message}")
if (attempt == times - 1) throw e
}
}
return null
}

fun main() {
var count = 0

try {
val result = retry(3) {
count++
if (count < 3) {
throw Exception("まだ準備できていません")
}
"成功!"
}
println(result)
} catch (e: Exception) {
println("最終的に失敗")
}
}

リソースのクリーンアップ

class Resource : AutoCloseable {
init {
println("リソース作成")
}

fun use() {
println("リソース使用")
}

override fun close() {
println("リソースクリーンアップ")
}
}

fun main() {
try {
Resource().use { resource ->
resource.use()
// 自動的にclose()呼び出し
}
} catch (e: Exception) {
println("エラー発生")
}
}

🤔 よくある質問

Q1. throwはいつ使いますか?

A: 明示的に例外を発生させるときです!

fun validateAge(age: Int) {
if (age < 0) {
throw IllegalArgumentException("年齢は負の数にできません")
}
if (age > 150) {
throw IllegalArgumentException("年齢が大きすぎます")
}
}

Q2. Exception vs RuntimeException?

A: Kotlinは区別がありません!

// Javaではchecked/uncheckedの区別がある
// Kotlinはすべてunchecked (throws宣言不要)

fun divide(a: Int, b: Int): Int {
return a / b // throws宣言不要!
}

Q3. カスタム例外は?

A: Exceptionを継承します!

class InvalidEmailException(message: String) : Exception(message)

fun validateEmail(email: String) {
if (!email.contains("@")) {
throw InvalidEmailException("無効なメールアドレス: $email")
}
}

fun main() {
try {
validateEmail("invalid")
} catch (e: InvalidEmailException) {
println(e.message)
}
}

🎬 まとめ

例外処理で安定したプログラムを作成しましょう!

要点まとめ:
✅ try-catchで例外処理
✅ require/checkで検証
✅ runCatchingで簡単に
✅ 式として値を返す
✅ finallyでクリーンアップ作業

次のステップ: ファイル入出力でファイルを扱ってみましょう!