Как перехватить несколько исключений одновременно в Kotlin?
Проблема множественного перехвата в Kotlin
В Kotlin, в отличие от Java, нет синтаксиса с вертикальной чертой для объединения исключений в одном блоке catch. Однако это не значит, что вы не можете обработать несколько исключений лаконично. Есть несколько идиоматических способов.
Способ 1: Использование when в блоке catch
Самый распространённый и читаемый подход — использовать одно исключение базового типа (например, Exception) и проверять его тип с помощью when:
try {
// Код, который может выбросить NumberFormatException или IOException
val number = "abc".toInt()
val file = File("test.txt").readText()
} catch (e: Exception) {
when (e) {
is NumberFormatException -> println("Ошибка формата числа: ${e.message}")
is IOException -> println("Ошибка ввода-вывода: ${e.message}")
else -> throw e // пробрасываем неизвестные исключения
}
}
Важно: Если вы используете catch (e: Exception), вы перехватываете все исключения. Не забывайте про else -> throw e, чтобы не проглотить неожиданные ошибки (например, NullPointerException).
Способ 2: Несколько блоков catch
В Kotlin можно написать несколько блоков catch подряд. Это работает, но выглядит громоздко, если исключений много:
try {
// код
} catch (e: NumberFormatException) {
println("Ошибка формата числа")
} catch (e: IOException) {
println("Ошибка ввода-вывода")
}
Способ 3: Функция-расширение для группировки (продвинутый)
Если вы часто сталкиваетесь с такой задачей, можно написать вспомогательную функцию:
inline fun <R> tryOr(vararg exceptions: KClass<out Throwable>, block: () -> R, handler: (Throwable) -> Unit) {
try {
block()
} catch (e: Throwable) {
if (exceptions.any { it.isInstance(e) }) {
handler(e)
} else {
throw e
}
}
}
// Использование:
tryOr(NumberFormatException::class, IOException::class,
block = {
val number = "abc".toInt()
val file = File("test.txt").readText()
},
handler = { e ->
println("Обработано: ${e::class.simpleName} - ${e.message}")
}
)
Способ 4: Sealed class для единой обработки (рекомендуемый)
Лучший способ для сложных сценариев — создать sealed class, который оборачивает разные исключения в единый тип:
sealed class AppError {
data class ParseError(val message: String) : AppError()
data class FileError(val message: String) : AppError()
}
fun process(): AppError? {
return try {
val number = "abc".toInt()
val file = File("test.txt").readText()
null // успех
} catch (e: NumberFormatException) {
AppError.ParseError(e.message ?: "Ошибка парсинга")
} catch (e: IOException) {
AppError.FileError(e.message ?: "Ошибка файла")
}
}
// Использование:
val error = process()
when (error) {
is AppError.ParseError -> println("Парсинг: ${error.message}")
is AppError.FileError -> println("Файл: ${error.message}")
null -> println("Успех")
}
Итог
- Для простых случаев используйте
when (e)внутри одного catch. - Для чистоты кода в больших проектах — sealed class.
- Избегайте пустых catch-блоков — это скрывает ошибки.
Альтернативный подход: использование Result и runCatching
В Kotlin есть функциональный способ обработки исключений — runCatching. Он возвращает объект Result<T>, который можно обработать без блоков try-catch:
val result = runCatching {
val number = "abc".toInt()
val file = File("test.txt").readText()
// возвращаем что-то или Unit
}
result.onFailure { e ->
when (e) {
is NumberFormatException -> println("Число не распарсено")
is IOException -> println("Проблема с файлом")
else -> println("Неизвестная ошибка: ${e.message}")
}
}
Плюсы:
- Нет вложенных try-catch.
- Можно комбинировать с цепочками вызовов (
map,recover). - Подходит для функционального стиля.
Минусы:
- Нельзя пробросить checked исключения (но в Kotlin их нет).
- Требует Kotlin 1.3+.
Этот способ особенно удобен, когда нужно обработать ошибку и продолжить выполнение без прерывания потока.