Spec-Zone.ru › Kotlin 2

Обработка исключений и ошибок

Исключения помогают сделать выполнение кода более предсказуемым, даже если возникают ошибки во время выполнения, которые могут нарушить работу программы. По умолчанию Kotlin считает все исключения непроверяемыми. Непроверяемые исключения упрощают обработку исключений: вы можете перехватывать исключения, но вам не нужно явно обрабатывать или объявлять их.

Подробнее о том, как Kotlin обрабатывает исключения при взаимодействии с Java, Swift и Objective-C, см. в разделе Взаимодействие исключений с Java, Swift и Objective-C.

Работа с исключениями включает два основных действия:

  • Генерация исключений: обозначает возникновение проблемы.

  • Перехват исключений: вручную обрабатывает неожиданное исключение, устраняя проблему или уведомляя разработчика либо пользователя приложения.

Исключения представлены подклассами класса Exception, который является подклассом класса Throwable. Подробнее об иерархии см. в разделе Иерархия исключений. Поскольку Exception — это open class, вы можете создавать пользовательские исключения, чтобы удовлетворить конкретные потребности вашего приложения.

Генерация исключений

Вы можете вручную генерировать исключения с помощью ключевого слова throw. Генерация исключения указывает на то, что в коде произошла неожиданная ошибка во время выполнения. Исключения — это объекты, и генерация исключения создает экземпляр класса исключения.

Исключение можно сгенерировать без параметров:

throw IllegalArgumentException()

Чтобы лучше понять источник проблемы, добавьте дополнительную информацию, например пользовательское сообщение и исходную причину:

val cause = IllegalStateException("Original cause: illegal state")

// Throws an IllegalArgumentException if userInput is negative 
// Additionally, it shows the original cause, represented by the cause IllegalStateException
if (userInput < 0) {
    throw IllegalArgumentException("Input must be non-negative", cause)
}

В этом примере IllegalArgumentException генерируется, если пользователь вводит отрицательное значение. Вы можете создавать собственные сообщения об ошибках и сохранять исходную причину (cause) исключения, которая будет включена в трассировку стека.

Генерация исключений с помощью функций предварительной проверки

В Kotlin есть дополнительные способы автоматически генерировать исключения с помощью функций предварительной проверки. К таким функциям относятся:

Функция предварительной проверки

Назначение

Генерируемое исключение

require()

Проверяет допустимость пользовательского ввода

IllegalArgumentException

check()

Проверяет допустимость состояния объекта или переменной

IllegalStateException

error()

Обозначает недопустимое состояние или условие

IllegalStateException

Эти функции подходят для ситуаций, когда выполнение программы не может продолжаться, если определенные условия не соблюдены. Это позволяет сделать код компактнее и эффективно выполнять такие проверки.

Функция require()

Используйте функцию require() для проверки входных аргументов, если они важны для работы функции и функция не может продолжить выполнение с недопустимыми аргументами.

Если условие в require() не выполняется, генерируется IllegalArgumentException:

fun getIndices(count: Int): List<Int> {
    require(count >= 0) { "Count must be non-negative. You set count to $count." }
    return List(count) { it + 1 }
}

fun main() {
    // This fails with an IllegalArgumentException
    println(getIndices(-1))
    
    // Uncomment the line below to see a working example
    // println(getIndices(3))
    // [1, 2, 3]
}

Функция require() позволяет компилятору выполнять умное приведение типов. После успешной проверки переменная автоматически приводится к не допускающему значение null типу. Эти функции часто используют для проверки допуска null, чтобы убедиться, что переменная не равна null, прежде чем продолжить выполнение. Например:

fun printNonNullString(str: String?) {
    // Nullability check
    require(str != null) 
    // After this successful check, 'str' is guaranteed to be 
    // non-null and is automatically smart cast to non-nullable String
    println(str.length)
}

Функция check()

Используйте функцию check() для проверки состояния объекта или переменной. Если проверка не проходит, это указывает на логическую ошибку, которую необходимо устранить.

Если условие, заданное в функции check(), равно false, генерируется IllegalStateException:

fun main() {
    var someState: String? = null

    fun getStateValue(): String {

        val state = checkNotNull(someState) { "State must be set beforehand!" }
        check(state.isNotEmpty()) { "State must be non-empty!" }
        return state
    }
    // If you uncomment the line below then the program fails with IllegalStateException
    // getStateValue()

    someState = ""

    // If you uncomment the line below then the program fails with IllegalStateException
    // getStateValue() 
    someState = "non-empty-state"

    // This prints "non-empty-state"
    println(getStateValue())
}

Функция check() позволяет компилятору выполнять умное приведение типов. После успешной проверки переменная автоматически приводится к не допускающему значение null типу. Эти функции часто используют для проверки допуска null, чтобы убедиться, что переменная не равна null, прежде чем продолжить выполнение. Например:

fun printNonNullString(str: String?) {
    // Nullability check
    check(str != null) 
    // After this successful check, 'str' is guaranteed to be 
    // non-null and is automatically smart cast to non-nullable String
    println(str.length)
}

Функция error()

Функция error() используется для обозначения недопустимого состояния или условия в коде, которое, согласно логике программы, не должно возникать. Она подходит для ситуаций, когда вы намеренно хотите сгенерировать исключение в коде, например при неожиданном состоянии. Эта функция особенно полезна в выражениях when, позволяя явно обработать случаи, которые не должны возникать с точки зрения логики программы.

В следующем примере функция error() используется для обработки неопределенной роли пользователя. Если роль не входит в список предопределенных, генерируется IllegalStateException:

class User(val name: String, val role: String)

fun processUserRole(user: User) {
    when (user.role) {
        "admin" -> println("${user.name} is an admin.")
        "editor" -> println("${user.name} is an editor.")
        "viewer" -> println("${user.name} is a viewer.")
        else -> error("Undefined role: ${user.role}")
    }
}

fun main() {
    // This works as expected
    val user1 = User("Alice", "admin")
    processUserRole(user1)
    // Alice is an admin.

    // This throws an IllegalStateException
    val user2 = User("Bob", "guest")
    processUserRole(user2)
}

Обработка исключений с помощью блоков try-catch

При генерации исключения нормальное выполнение программы прерывается. Чтобы программа оставалась стабильной, можно корректно обрабатывать исключения с помощью ключевых слов try и catch. Блок try содержит код, который может сгенерировать исключение, а блок catch перехватывает и обрабатывает исключение, если оно возникает. Исключение перехватывается первым блоком catch, соответствующим его типу или суперклассу исключения.

Вот как можно использовать вместе ключевые слова try и catch:

try {
    // Code that may throw an exception
} catch (e: SomeException) {
    // Code for handling the exception
}

Часто try-catch используют как выражение, чтобы оно могло возвращать значение из блока try или блока catch:

fun main() {
    val num: Int = try {

        // If count() completes successfully, its return value is assigned to num
        count()
        
    } catch (e: ArithmeticException) {
        
        // If count() throws an exception, the catch block returns -1, 
        // which is assigned to num
        -1
    }
    println("Result: $num")
}

// Simulates a function that might throw ArithmeticException
fun count(): Int {
    
    // Change this value to return a different value to num
    val a = 0
    
    return 10 / a
}

Исключение можно обработать и без использования его экземпляра. Например, в блоке catch можно задать резервное значение или общее сообщение об ошибке.

Используйте знак подчеркивания (_) вместо имени параметра исключения, чтобы указать, что экземпляр исключения намеренно игнорируется:

import java.io.File
import java.io.IOException

//sampleStart
fun main() {
    val userSettings = try {
        File("user-settings.json").readText()
    
    // Catches IOException without using the exception instance
    } catch (_: IOException) {
        // Uses a fallback value if loading the file fails
        "{}"
    }

    println(userSettings)
}
//sampleEnd

Для одного блока try можно использовать несколько обработчиков catch. При необходимости добавьте несколько блоков catch, чтобы по-разному обрабатывать различные исключения. Если блоков catch несколько, важно упорядочить их от наиболее конкретного исключения к наименее конкретному, сверху вниз в коде. Такой порядок соответствует потоку выполнения программы.

Рассмотрим пример с пользовательскими исключениями:

open class WithdrawalException(message: String) : Exception(message)
class InsufficientFundsException(message: String) : WithdrawalException(message)

fun processWithdrawal(amount: Double, availableFunds: Double) {
    if (amount > availableFunds) {
        throw InsufficientFundsException("Insufficient funds for the withdrawal.")
    }
    if (amount < 1 || amount % 1 != 0.0) {
        throw WithdrawalException("Invalid withdrawal amount.")
    }
    println("Withdrawal processed")
}

fun main() {
    val availableFunds = 500.0

    // Change this value to test different scenarios
    val withdrawalAmount = 500.5

    try {
        processWithdrawal(withdrawalAmount.toDouble(), availableFunds)

    // The order of catch blocks is important!
    } catch (e: InsufficientFundsException) {
        println("Caught an InsufficientFundsException: ${e.message}")
    } catch (e: WithdrawalException) {
        println("Caught a WithdrawalException: ${e.message}")
    }
}

Общий блок catch для обработки WithdrawalException перехватывает все исключения этого типа, в том числе конкретные, например InsufficientFundsException, если они не были перехвачены ранее более конкретным блоком catch.

Блок finally

Блок finally содержит код, который выполняется всегда, независимо от того, завершился ли блок try успешно или сгенерировал исключение. С помощью блока finally можно очистить ресурсы после выполнения блоков try и catch. Это особенно важно при работе с такими ресурсами, как файлы или сетевые соединения, поскольку finally гарантирует их корректное закрытие или освобождение.

Вот типичный пример совместного использования блоков try-catch-finally:

try {
    // Code that may throw an exception
}
catch (e: YourException) {
    // Exception handler
}
finally {
    // Code that is always executed
}

Возвращаемое значение выражения try определяется последним выполненным выражением в блоке try или catch. Если исключений не возникает, результат берется из блока try; если исключение обработано — из блока catch. Блок finally выполняется всегда, но не изменяет результат блока try-catch.

Рассмотрим пример:

fun divideOrNull(a: Int): Int {
    
    // The try block is always executed
    // An exception here (division by zero) causes an immediate jump to the catch block
    try {
        val b = 44 / a
        println("try block: Executing division: $b")
        return b
    }
    
    // The catch block is executed due to the ArithmeticException (division by zero if a ==0)
    catch (e: ArithmeticException) {
        println("catch block: Encountered ArithmeticException $e")
        return -1
    }
    finally {
        println("finally block: The finally block is always executed")
    }
}

fun main() {
    
    // Change this value to get a different result. An ArithmeticException will return: -1
    divideOrNull(0)
}

В Kotlin идиоматичный способ управления ресурсами, реализующими интерфейс AutoClosable, например файловыми потоками FileInputStream или FileOutputStream, — использовать функцию .use(). Эта функция автоматически закрывает ресурс по завершении блока кода независимо от того, было ли сгенерировано исключение, поэтому блок finally не нужен. Следовательно, для управления ресурсами в Kotlin не требуется специальный синтаксис, такой как try-with-resources в Java.

FileWriter("test.txt").use { writer ->
    writer.write("some text")
    // After this block, the .use function automatically calls writer.close(), similar to a finally block
}

Если в коде нужно очистить ресурсы без обработки исключений, можно также использовать try с блоком finally без блоков catch:

class MockResource { 
    fun use() { 
        println("Resource being used") 
        // Simulate a resource being used 
        // This throws an ArithmeticException if division by zero occurs
        val result = 100 / 0
        
        // This line is not executed if an exception is thrown
        println("Result: $result") 
    }
    
    fun close() { 
        println("Resource closed") 
    }
}

fun main() { 
    val resource = MockResource()
//sampleStart 
    try {
        
        // Attempts to use the resource 
        resource.use()
        
    } finally {
        
        // Ensures that the resource is always closed, even if an exception occurs 
        resource.close()
    }

    // This line is not printed if an exception is thrown
    println("End of the program")
//sampleEnd
}

Как видите, блок finally гарантирует закрытие ресурса независимо от того, возникло ли исключение.

В Kotlin можно использовать только блок catch, только блок finally или оба — в зависимости от конкретной задачи. Однако блок try всегда должен сопровождаться как минимум одним блоком catch или блоком finally.

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

В Kotlin можно определять пользовательские исключения, создавая классы, расширяющие встроенный класс Exception. Это позволяет создавать более конкретные типы ошибок, соответствующие потребностям вашего приложения.

Чтобы создать такое исключение, определите класс, расширяющий Exception:

class MyException: Exception("My message")

В этом примере задано сообщение об ошибке по умолчанию «My message», но при желании его можно оставить пустым.

Исключения в Kotlin — это объекты с состоянием, содержащие информацию о контексте своего создания, который представлен в виде трассировки стека. Не создавайте исключения с помощью объявлений объектов. Вместо этого каждый раз, когда нужно исключение, создавайте его новый экземпляр. Так вы гарантируете, что состояние исключения точно отражает конкретный контекст.

Пользовательские исключения также могут быть подклассами любых существующих подклассов исключений, например подкласса ArithmeticException:

class NumberTooLargeException: ArithmeticException("My message")

Если вы хотите создавать подклассы пользовательских исключений, необходимо объявить родительский класс как open, поскольку по умолчанию классы являются final и иначе от них нельзя унаследоваться.

Например:

// Declares a custom exception as an open class, making it subclassable
open class MyCustomException(message: String): Exception(message)

// Creates a subclass of the custom exception
class SpecificCustomException: MyCustomException("Specific error message")

Пользовательские исключения ведут себя так же, как встроенные. Их можно генерировать с помощью ключевого слова throw и обрабатывать в блоках try-catch-finally. Рассмотрим пример:

class NegativeNumberException: Exception("Parameter is less than zero.")
class NonNegativeNumberException: Exception("Parameter is a non-negative number.")

fun myFunction(number: Int) {
    if (number < 0) throw NegativeNumberException()
    else if (number >= 0) throw NonNegativeNumberException()
}

fun main() {
    
    // Change the value in this function to a get a different exception
    myFunction(1)
}

В приложениях с различными сценариями ошибок создание иерархии исключений помогает сделать код более понятным и конкретным. Для этого можно использовать абстрактный класс или запечатанный класс в качестве основы для общих характеристик исключений, а для отдельных типов исключений создать конкретные подклассы. Кроме того, пользовательские исключения с параметрами, имеющими значения по умолчанию, обеспечивают гибкость: их можно инициализировать с разными сообщениями, что позволяет более детально обрабатывать ошибки.

Рассмотрим пример, в котором запечатанный класс AccountException используется как основа иерархии исключений, а класс APIKeyExpiredException — как подкласс. В примере показано использование параметров со значениями по умолчанию для более подробного описания исключений:

//sampleStart
// Creates a sealed class as the base for an exception hierarchy for account-related errors
sealed class AccountException(message: String, cause: Throwable? = null):
Exception(message, cause)

// Creates a subclass of AccountException
class InvalidAccountCredentialsException : AccountException("Invalid account credentials detected")

// Creates a subclass of AccountException, which allows the addition of custom messages and causes
class APIKeyExpiredException(message: String = "API key expired", cause: Throwable? = null)	: AccountException(message, cause)

// Change values of placeholder functions to get different results
fun areCredentialsValid(): Boolean = true
fun isAPIKeyExpired(): Boolean = true
//sampleEnd

// Validates account credentials and API key
fun validateAccount() {
    if (!areCredentialsValid()) throw InvalidAccountCredentialsException()
    if (isAPIKeyExpired()) {
        // Example of throwing APIKeyExpiredException with a specific cause
        val cause = RuntimeException("API key validation failed due to network error")
        throw APIKeyExpiredException(cause = cause)
    }
}

fun main() {
    try {
        validateAccount()
        println("Operation successful: Account credentials and API key are valid.")
    } catch (e: AccountException) {
        println("Error: ${e.message}")
        e.cause?.let { println("Caused by: ${it.message}") }
    }
}

Тип Nothing

В Kotlin у каждого выражения есть тип. Тип выражения throw IllegalArgumentException() — Nothing, встроенный тип, являющийся подтипом всех остальных типов и также известный как нижний тип. Это означает, что Nothing можно использовать как тип возвращаемого значения или обобщенный тип там, где ожидается любой другой тип, не вызывая ошибок типов.

Nothing — специальный тип в Kotlin, обозначающий функции или выражения, которые никогда не завершаются успешно: они либо всегда генерируют исключение, либо переходят в бесконечный путь выполнения, например бесконечный цикл. С помощью Nothing можно помечать функции, которые еще не реализованы или всегда должны генерировать исключение, явно указывая на это компилятору и читателям кода. Если компилятор выводит тип Nothing в сигнатуре функции, он выдаст предупреждение. Явное указание Nothing в качестве типа возвращаемого значения позволяет устранить это предупреждение.

В этом примере кода на Kotlin показано использование типа Nothing: компилятор помечает код после вызова функции как недостижимый:

class Person(val name: String?)

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
    // This function will never return successfully.
    // It will always throw an exception.
}

fun main() {
    // Creates an instance of Person with 'name' as null
    val person = Person(name = null)
    
    val s: String = person.name ?: fail("Name required")

    // 's' is guaranteed to be initialized at this point
    println(s)
}

Функция TODO() в Kotlin, которая также использует тип Nothing, служит заглушкой и выделяет участки кода, которые нужно реализовать в будущем:

fun notImplementedFunction(): Int {
    TODO("This function is not yet implemented")
}

fun main() {
    val result = notImplementedFunction()
    // This throws a NotImplementedError
    println(result)
}

Как видите, функция TODO() всегда генерирует исключение NotImplementedError.

Классы исключений

Рассмотрим некоторые распространенные типы исключений в Kotlin. Все они являются подклассами класса RuntimeException:

  • ArithmeticException: это исключение возникает, когда арифметическую операцию невозможно выполнить, например при делении на ноль.

    val example = 2 / 0 // throws ArithmeticException
    
  • IndexOutOfBoundsException: это исключение генерируется, чтобы указать, что индекс, например индекс массива или строки, выходит за допустимые границы.

    val myList = mutableListOf(1, 2, 3)
    myList.removeAt(3)  // throws IndexOutOfBoundsException
    

    Чтобы избежать этого исключения, используйте более безопасную альтернативу, например функцию getOrNull():

    val myList = listOf(1, 2, 3)
    // Returns null, instead of IndexOutOfBoundsException
    val element = myList.getOrNull(3)
    println("Element at index 3: $element")
    
  • NoSuchElementException: это исключение генерируется при обращении к элементу, которого нет в коллекции. Оно возникает при использовании методов, ожидающих наличие конкретного элемента, например first() или last().

    val emptyList = listOf<Int>()
    val firstElement = emptyList.first()  // throws NoSuchElementException
    

    Чтобы избежать этого исключения, используйте более безопасную альтернативу, например функцию firstOrNull():

    val emptyList = listOf<Int>()
    // Returns null, instead of NoSuchElementException
    val firstElement = emptyList.firstOrNull()
    println("First element in empty list: $firstElement")
    
  • NumberFormatException: это исключение возникает при попытке преобразовать строку в числовой тип, если строка имеет неподходящий формат.

    val string = "This is not a number"
    val number = string.toInt() // throws NumberFormatException
    

    Чтобы избежать этого исключения, используйте более безопасную альтернативу, например функцию toIntOrNull():

    val nonNumericString = "not a number"
    // Returns null, instead of NumberFormatException
    val number = nonNumericString.toIntOrNull()
    println("Converted number: $number")
    
  • NullPointerException: это исключение генерируется, когда приложение пытается использовать ссылку на объект со значением null. Хотя функции Kotlin для обеспечения безопасности при работе с null значительно снижают риск возникновения NullPointerException, такие исключения все еще возможны при намеренном использовании оператора !! или при взаимодействии с Java, в которой нет механизмов Kotlin для обеспечения безопасности при работе с null.

    val text: String? = null
    println(text!!.length)  // throws a NullPointerException
    

Хотя в Kotlin все исключения являются непроверяемыми и их не нужно явно перехватывать, вы все равно можете перехватывать их при необходимости.

Иерархия исключений

Корневым классом иерархии исключений Kotlin является Throwable. У него есть два непосредственных подкласса: Error и Exception:

  • Подкласс Error представляет серьезные фундаментальные проблемы, от которых приложение может не суметь восстановиться самостоятельно. Обычно такие проблемы не пытаются обрабатывать, например OutOfMemoryError или StackOverflowError.

  • Подкласс Exception используется для ситуаций, которые можно обработать. Подтипы типа Exception, например RuntimeException и IOException (исключение ввода-вывода), предназначены для обработки исключительных ситуаций в приложениях.

Exception hierarchy - the Throwable class

RuntimeException обычно возникает из-за недостаточных проверок в коде программы, и его можно предотвратить программно. Kotlin помогает предотвратить распространенные случаи RuntimeExceptions, например NullPointerException, а также выдает предупреждения во время компиляции о потенциальных ошибках времени выполнения, таких как деление на ноль. На следующем изображении показана иерархия подтипов, производных от RuntimeException:

Hierarchy of RuntimeExceptions

Трассировка стека

Трассировка стека — это отчет, создаваемый средой выполнения и используемый для отладки. Он показывает последовательность вызовов функций, приведших к определенной точке программы, особенно к месту возникновения ошибки или исключения.

Рассмотрим пример, в котором трассировка стека автоматически выводится из-за исключения в среде JVM:

fun main() {
//sampleStart    
    throw ArithmeticException("This is an arithmetic exception!")
//sampleEnd    
}

При запуске этого кода в среде JVM выводится следующий результат:

Exception in thread "main" java.lang.ArithmeticException: This is an arithmetic exception!
    at MainKt.main(Main.kt:3)
    at MainKt.main(Main.kt)

Первая строка содержит описание исключения, в которое входят:

  • Тип исключения: java.lang.ArithmeticException

  • Поток: main

  • Сообщение исключения: "This is an arithmetic exception!"

Каждая следующая строка, начинающаяся с at после описания исключения, относится к трассировке стека. Отдельная строка называется элементом трассировки стека или кадром стека:

  • at MainKt.main (Main.kt:3): здесь указано имя метода (MainKt.main), а также исходный файл и номер строки, где был вызван метод (Main.kt:3).

  • at MainKt.main (Main.kt): это показывает, что исключение возникло в функции main() файла Main.kt.

Взаимодействие исключений с Java, Swift и Objective-C

Поскольку Kotlin считает все исключения непроверяемыми, при вызове такого кода из языков, различающих проверяемые и непроверяемые исключения, могут возникнуть сложности. Чтобы устранить различия в обработке исключений между Kotlin и такими языками, как Java, Swift и Objective-C, можно использовать аннотацию @Throws. Эта аннотация сообщает вызывающему коду о возможных исключениях. Подробнее см. в разделах Вызов Kotlin из Java и Взаимодействие со Swift/Objective-C.

12 августа 2026 г.
Возвраты и переходыФункции

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/exceptions.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API