Avatar ·

Как перехватить несколько исключений одновременно в Kotlin?

Всем привет! Я начал изучать Kotlin и столкнулся с проблемой обработки исключений. В Java я привык писать `catch (IOException | SQLException e)`, чтобы перехватывать несколько типов исключений одним блоком. Но в Kotlin такой синтаксис не работает. Мне нужно обработать ситуации, когда при парсинге числа из строки может вылететь `NumberFormatException`, а при обращении к файлу — `FileNotFoundException` и `IOException`. В Java я бы написал: ```java try { // какой-то код } catch (IOException | NumberFormatException e) { // обработка } ``` А в Kotlin я пробовал так: ```kotlin try { // код } catch (e: IOException | NumberFormatException) { // не компилируется // обработка } ``` Но компилятор ругается. Подскажите, как правильно перехватывать несколько исключений в одном блоке catch в 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+.

Этот способ особенно удобен, когда нужно обработать ошибку и продолжить выполнение без прерывания потока.