В чём разница между исключениями в Java и Kotlin?

Разница между исключениями в Java и Kotlin касается в основном обязательности обработки, синтаксиса, а также подхода к исключениям как части языка. Kotlin унаследовал модель исключений Java, но упростил и упростил её использование.

🔹 1. Проверяемые и непроверяемые исключения

В Java:

Java делит исключения на два типа:

  • Проверяемые (checked) — наследники Exception, но не RuntimeException.

    • Компилятор требует либо обработать их (try-catch), либо объявить в throws.

    • Пример: IOException, SQLException.

  • Непроверяемые (unchecked) — наследники RuntimeException или Error.

    • Не требуют явной обработки.

    • Пример: NullPointerException, IllegalArgumentException.

public void readFile() throws IOException {
FileReader reader = new FileReader("file.txt"); // must handle IOException
}

В Kotlin:

Нет checked-исключений вообще.
Все исключения в Kotlin — непроверяемые (как RuntimeException в Java).

fun readFile() {
val reader = FileReader("file.txt") // можно не оборачивать в try-catch
}

Компилятор не требует try-catch или throws.

🔸 2. Синтаксис обработки

Синтаксис try-catch-finally очень похож, но в Kotlin он более выразителен, потому что try — это выражение, а не просто блок:

Java:

try {
int x = 1 / 0;
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("Done");
}

Kotlin:

val result = try {
1 / 0
} catch (e: ArithmeticException) {
0
} finally {
println("Done")
}

→ try может возвращать значение, как if или when.

🔹 3. Нет throws в сигнатуре

В Java вы должны явно указывать в сигнатуре, какие checked-исключения может выбросить метод:

public void risky() throws IOException

В Kotlin нет ключевого слова throws в сигнатурах по умолчанию, и компилятор не проверяет это. Хотя вы можете использовать аннотацию @Throws для совместимости с Java:

@Throws(IOException::class)
fun risky() { ... }

🔸 4. Исключения из Java в Kotlin

Kotlin может вызывать Java-код, выбрасывающий checked-исключения, без обязательного try-catch.

val reader = FileReader("file.txt") // IOException возможен, но обработка не обязательна

🔹 5. Создание и выбрасывание исключений

И в Java, и в Kotlin для генерации исключения используется throw, но в Kotlin throw — это выражение типа Nothing, что позволяет использовать его, например, в выражениях:

val name = input ?: throw IllegalArgumentException("Name required")

→ Это невозможно в Java так элегантно.

🔸 6. Пользовательские исключения

Создание кастомных исключений почти одинаково:

Java:

public class MyException extends Exception {
public MyException(String message) {
super(message);
}
}

Kotlin:

class MyException(message: String) : Exception(message)

🧠 Сравнение по ключевым аспектам

Аспект Java Kotlin
Checked-исключения ✅ Да (обязательны к обработке) ❌ Нет (все исключения — unchecked)
--- --- ---
throws в сигнатуре ✅ Обязателен для checked ❌ Не используется (кроме @Throws для Java)
--- --- ---
try как выражение ❌ Нет ✅ Да
--- --- ---
Тип throw void Nothing (можно использовать в выражениях)
--- --- ---
Компилятор требует catch? ✅ Для checked ❌ Нет
--- --- ---

📦 Вывод

  • Kotlin упрощает работу с исключениями, устранив checked-исключения и сделав try выражением.

  • В Kotlin вы обрабатываете ошибки по желанию, а не по требованию компилятора.

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