Обработка ошибок kotlin

Исключения

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

Все исключения в Kotlin являются наследниками класса Throwable.
У каждого исключения есть сообщение, трассировка стека и (опционально) причина, по которой
это исключение вероятно было вызвано.

Для того чтобы возбудить исключение явным образом, используйте оператор throw.

throw Exception("Hi There!")

Чтобы перехватить исключение, используйте выражение trycatch.

try {
    // some code
} catch (e: SomeException) {
    // handler
} finally {
    // optional finally block
}

В коде может быть любое количество блоков catch (такие блоки могут и вовсе отсутствовать). Блоки finally
могут быть опущены. Однако, должен быть использован как минимум один блок catch или finally.

Try — это выражение

try является выражением, что означает, что оно может иметь возвращаемое значение.

val a: Int? = try { input.toInt() } catch (e: NumberFormatException) { null }

Возвращаемым значением будет либо последнее выражение в блоке try, либо последнее выражение
в блоке catch (или блоках). Содержимое finally блока никак не повлияет на результат try-выражения.

Проверяемые исключения

В Kotlin нет проверяемых исключений. Для этого существует целый ряд причин, но мы рассмотрим простой пример, который иллюстрирует причину этого.

Приведённый ниже фрагмент кода является примером простого интерфейса в JDK, который реализован в классе StringBuilder.

Appendable append(CharSequence csq) throws IOException;

Сигнатура говорит, что каждый раз, когда я присоединяю строку к чему-то (к StringBuilder, какому-нибудь логу, сообщению в консоль и т.п),
мне необходимо отлавливать исключения типа IOExceptions. Почему? Потому, что данная операция может вызывать IO (Input-Output: Ввод-Вывод) (Writer также
реализует интерфейс Appendable).
Данный факт постоянно приводит к написанию подобного кода:

try {
    log.append(message)
} catch (IOException e) {
    // Должно быть безопасно
}

И это плохо. См. Effective Java, Item 77: Don’t ignore exceptions (не игнорируйте исключения).

Брюс Эккель как-то сказал о проверяемых исключения:

Анализ небольших программ показал, что обязательная обработка исключений может повысить производительность разработчика и улучшить качество кода.
Однако, изучение крупных проектов по разработке программного обеспечения позволяет сделать противоположный вывод:
происходит понижение продуктивности и сравнительно небольшое улучшение кода (а иногда и без всякого улучшения).

Вот несколько других рассуждений по этому поводу:

  • Java’s checked exceptions were a mistake (Rod Waldhoff)
  • The Trouble with Checked Exceptions (Anders Hejlsberg)

Если вы хотите предупредить вызывающие объекты о возможных исключениях при вызове Kotlin кода из Java, Swift или Objective-C,
вы можете использовать аннотацию @Throws. Узнайте больше об использовании этой аннотации для Java,
а также для Swift и Objective-C.

Тип Nothing

throw в Kotlin является выражением, поэтому есть возможность использовать его, например, в качестве части Elvis-выражения:

val s = person.name ?: throw IllegalArgumentException("Name required")

Типом выражения throw является специальный тип под названием Nothing.
У этого типа нет никаких значений, он используется для того, чтобы обозначить те участки кода, которые могут быть не достигнуты никогда.
В своём коде вы можете использовать Nothing для того, чтобы отметить функцию, чей результат никогда не будет возвращён.

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}

При вызове такой функции компилятор будет в курсе, что исполнения кода далее не последует:

val s = person.name ?: fail("Name required")
println(s) // известно, что переменная 's' проинициализирована к этому моменту

Вы также можете столкнуться с этим типом при работе с выводом типов.
Nullable-вариант этого типа Nothing? имеет ровно одно возможное значение — null.
Если вы используете null для инициализации значения предполагаемого типа, и нет никакой другой информации,
которую можно использовать для определения более конкретного типа, компилятор определит тип Nothing?.

val x = null           // у 'x' тип `Nothing?`
val l = listOf(null)   // у 'l' тип `List<Nothing?>

Совместимость с Java

См. раздел, посвящённый исключениям, Страница совместимости Java для получения информации о совместимости с Java.

Время на прочтение
8 мин

Количество просмотров 18K

Наша компания уже более двух лет использует Kotlin в продакшене. Лично я с этим языком столкнулся около года назад. Тут есть много тем для разговора, но сегодня поговорим об обработке ошибок, в том числе в функциональном стиле. Расскажу, как это можно делать в Kotlin.

image

(Фото с митапа по этой теме, проходившего в офисе одной из компаний Таганрога. Выступал Алексей Шафранов — лидер рабочей группы (Java) в «Максилект»)

Как можно в принципе обрабатывать ошибки?

Я нашел несколько путей:

  • можно использовать некое возвращаемое значение в качестве указателя на то, что есть ошибка;
  • можно с той же целью использовать параметр-индикатор,
  • ввести глобальную переменную,
  • обрабатывать исключения,
  • добавлять контракты (DbC).

Остановимся чуть подробнее на каждом из вариантов.

Возвращаемое значение

Некое “магическое” значение возвращается, если возникла ошибка. Если вы когда-либо использовали скриптовые языки, наверняка видели подобные конструкции.

Пример 1:

function sqrt(x) {
	if(x < 0)
	return -1;
	else
		return √x;
}

Пример 2:

function getUser(id) {
	result = db.getUserById(id)
	if (result)
		return result as User
	else
		return “Can’t find user ” + id
}

Параметр-индикатор

Используется некий передаваемый в функцию параметр. После возвращения значения по параметру можно посмотреть, была ли внутри функции ошибка.

Пример:

function divide(x,y,out Success) {
	if (y == 0)
		Success = false
	else
		Success = true
		return x/y
}
divide(10, 11, Success)
id (!Success)	//handle error

Глобальная переменная

Примерно так же работает и глобальная переменная.

Пример:

global Success = true
function divide(x,y) {
	if (y == 0)
		Success = false
	else
		return x/y
}
divide(10, 11, Success)
id (!Success)	//handle error

Исключения

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

Пример:

function divide(x,y) {
	if (y == 0)
		throw Exception()
	else
		return x/y
}
try{ divide(10, 0)}
catch (e) {//handle exception}

Контракты (DbC)

Откровенно говоря, вживую я этого подхода никогда не видел. Путем долгого гугления я нашел, что в Kotlin 1.3 есть библиотека, фактически позволяющая использовать contracts. Т.е. вы можете ставить condition на переменные, которые передаются в функцию, condition на возвращаемое значение, количество вызовов, то, откуда она вызывается и т.д. И если все условия выполняются, считается, что функция сработала правильно.

Пример:

function sqrt (x)
	pre-condition (x >= 0)
post-condition (return >= 0)
begin
	calculate sqrt from x
end

Честно говоря, эта библиотека отличается ужасным синтаксисом. Возможно, поэтому я и не видел подобного вживую.

Исключения в Java

Перейдем к Java и к тому, как все это изначально работало.

image

При проектировании языка заложили два типа исключений:

  • checked – проверяемые;
  • unchecked – непроверяемые.

Для чего нужны checked исключения? Теоретически они нужны, чтобы люди обязательно проверяли ошибки. Т.е. если возможно определенное checked исключение, в дальнейшем оно обязательно должно быть проверено. Теоретически такой подход должен был привести к отсутствию необработанных ошибок и повышению качества кода. Но на практике это не так. Думаю, каждый хотя бы раз в жизни видел пустой блок catch.

Почему это может быть плохо?

Вот классический пример прямо из документации по Kotlin – интерфейс из JDK, реализованный в StringBuilder:

Appendable append(CharSequence csq) throws IOException;

try {
	log.append(message)
}
catch (IOException e) {
	//Must be safe
}

Уверен, вы встречали достаточно много кода, обернутого в try-catch, где catch – пустой блок, поскольку такой ситуации просто не должно было произойти, по мнению разработчика. Во многих случаях обработка checked исключений реализуется следующим способом: просто бросают RuntimeException и где-то выше его ловят (или не ловят…).

try {
	// do something
}
catch (IOException e) {
	throw new RuntimeException(e); // там где-нибудь поймаю...

Что можно в Kotlin

С точки зрения исключений компилятор Kotlin отличается тем, что:

1. Не различает checked и unchecked исключения. Все исключения – только unchecked, и вы самостоятельно принимаете решение, стоит ли их отлавливать и обрабатывать.

2. Try можно использовать как выражение – можно запустить блок try и либо вернуть из него последнюю строчку, либо вернуть последнюю строчку из блока catch.

val value = try {Integer.parseInt(“lol”)}
	catch(e: NumberFormanException) { 4 } //Рандомное число

3. А также можно использовать подобную конструкцию при обращении к какому-либо объекту, который может быть nullable:

val s = obj.money
	?: throw IllegalArgumentException(“Где деньги, Лебовски”)

Совместимость с Java

Kotlin-код можно использовать в Java и наоборот. Как при этом обращаться с исключениями?

  • Проверяемые исключения из Java в Kotlin можно не проверять и не объявлять (поскольку в Kotlin нет проверяемых исключений).
  • Возможные проверяемые исключения из Kotlin (например, появившиеся изначально из Java) в Java проверять необязательно.
  • Если проверить необходимо, исключение можно сделать проверяемым, используя в методе аннотацию @Throws (необходимо указать, какие исключения этот метод может выбрасывать). Упомянутая аннотация нужна только для совместимости с Java. Но на практике у нас ее многие используют, чтобы декларировать, что подобный метод в принципе может передавать какие-то исключения.

Альтернатива блоку try-catch

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

try {
	HttpService.SendNotification(endpointUrl);
	MarkNotificationAsSent();
} catch (e: UnableToConnectToServerException) {
	MarkNotificationAsNotSent();
}

Какие есть альтернативы?

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

val result: Try<Result> =
Try{HttpService.SendNotification(endpointUrl)}

when(result) {
	is Success -> MarkNotificationAsSent()
	is Failure    -> MarkNotificationAsNotSent()
}

У нас есть возможность использовать монаду Try. По сути это контейнер, который хранит некоторое значение. flatMap – метод работы с этим контейнером, который вместе с текущим значением может принимать функцию и возвращать опять же монаду.

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

Функциональная обработка исключений

Откуда можно взять Try?

Во-первых, существует достаточно много реализаций классов Try и Either от сообщества. Можно взять их или даже написать реализацию самостоятельно. В одном из “боевых” проектов мы использовали самописную реализацию Try – обошлись одним классом и прекрасно справлялись.
Во-вторых, есть библиотека Arrow, которая в принципе добавляет много функциональщины в Kotlin. Естественно, там есть Try и Either.

Ну и кроме того, в Kotlin 1.3 появился класс Result, подробнее о котором я расскажу немного позже.

Try на примере библиотеки Arrow

Библиотека Arrow дает нам класс Try. Фактически он может быть в двух состояниях: Success или Failure:

  • Success при успешном выводе сохранит наше значение,
  • Failure хранит исключение, которое возникло в процессе выполнения блока кода.

Вызов выглядит следующим образом. Естественно, он обернут в обычный try – catch, но это будет происходить где-то внутри нашего кода.

sealed class Try<out A> {
	data class Success<out A>(val value: A) : Try<A>()
	data class Failure(val e: Throwable) : Try<Nothing>()

	companion object {
		operator fun <A> invoke(body: () -> A): Try<A> {
		return try {
			Success(body())
		} catch (e: Exception) {
			Failure(e)
		}
	}
}

Этот же класс должен реализовать метод flatMap, который позволяет передать функцию и вернуть нашу монаду try:

inline fun <B> map(f: (A) -> B): Try<B> =
	flatMap { Success(f(it)) }

inline fun <B> flatMap(f: (A) -> TryOf<B>): Try<B> =
	when (this) {
		is Failure -> this
		is Success -> f(value)
	}

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

val result1: Try<Int> = Try { 11 }
val result2: Try<Int> = Try { 4 }

val sum = result1.flatMap { one ->
	result2.map { two -> one + two }
}
println(sum) //Success(value=15)

Если оба вызова прошли успешно и мы получили значения, мы выполняем функцию. Если же они не успешны, то вернется Failure с исключением.

Вот как это выглядит, если что-то упало:

val result1: Try<Int> = Try { 11 }
val result2: Try<Int> = Try { throw RuntimeException(“Oh no!”) }

val sum = result1.flatMap { one ->
	result2.map { two -> one + two }
}
println(sum) //Failure(exception=java.lang.RuntimeException: Oh no!

Мы использовали ту же функцию, но на выходе получается Failure от RuntimeException.

Также библиотека Arrow позволяет использовать конструкции, которые по факту являются синтаксическим сахаром, в частности binding. Все то же самое можно переписать через последовательный flatMap, но binding позволяет сделать это читабельным.

val result1: Try<Int> = Try { 11 }
val result2: Try<Int> = Try { 4 }
val result3: Try<Int> = Try { throw RuntimeException(“Oh no, again!”) }

val sum = binding {
	val (one)   = result1
	val (two)   = result2
	val (three) = result3
	one + two + three
}
println(sum) //Failure(exception=java.lang.RuntimeException: Oh no, again!

Учитывая, что у нас один из результатов упал, мы получаем на выходе ошибку.

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

fun funA(): Try<Int> {
	return Try { 1 }
}
fun funB(): Try<Int> {
	Thread.sleep(3000L)
return Try { 2 }
}

val a = GlobalScope.async { funA() }
val b = GlobalScope.async { funB() }
val sum = runBlocking {
	a.await().flatMap { one ->
		b.await().map {two -> one + two }
	}
}

А вот более “боевой” пример. У нас есть запрос к серверу, мы его обрабатываем, получаем из него тело и пытаемся намапить его на наш класс, из которого уже возвращаем данные.

fun makeRequest(request: Request): Try<List<ResponseData>> =
	Try { httpClient.newCall(request).execute() }
		.map { it.body() }
		.flatMap { Try { ObjectMapper().readValue(it, ParsedResponse::class.java) } }
		.map { it.data }

fun main(args : Array<String>) {
	val response = makeRequest(RequestBody(args))
	when(response) {
		is Try.Success    -> response.data.toString()
		is Try.Failure       -> response.exception.message
	}
}

Try-catch сделал бы этот блок гораздо менее читабельным. А в данном случае мы на выходе получаем response.data, который можем обработать в зависимости от результата.

Result из Kotlin 1.3

В Kotlin 1.3 ввели класс Result. По факту он представляет собой нечто похожее на Try, но с рядом ограничений. Его изначально предполагается использовать для различных асинхронных операций.

val result: Result<VeryImportantData> = Result.runCatching { makeRequest() }
	.mapCatching { parseResponse(it) }
	.mapCatching { prepareData(it) }
result.fold{
	{ data -> println(“We have $data”) },
	exception -> println(“There is no any data, but it’s your exception $exception”) }
)

Если не ошибаюсь, этот класс на данный момент экспериментальный. Разработчики языка могут поменять его сигнатуру, поведение или вообще убрать, поэтому на данный момент его запрещено использовать в качестве возвращаемого значения из методов или переменной. Однако его можно использовать как локальную (приватную) переменную. Т.е. по факту его можно применять как try из примера.

Выводы

Выводы, которые я сделал лично для себя:

  • функциональная обработка ошибок в Kotlin – это просто и удобно;
  • никто не мешает обрабатывать их через try-catch в классическом стиле (и то, и то имеет право на жизнь; и то, и то удобно);
  • отсутствие проверяемых исключений не означает, что можно не обрабатывать ошибки;
  • непойманные исключения на продакшене приводят к печальным последствиям.

Автор статьи: Алексей Шафранов, лидер рабочей группы (Java), компания Maxilect

P.S. Мы публикуем наши статьи на нескольких площадках Рунета. Подписывайтесь на наши страницы в VK, FB или Telegram-канал, чтобы узнавать обо всех наших публикациях и других новостях компании Maxilect.

Дополнительные возможности ООП

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

Последнее обновление: 30.05.2021

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

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

try {
    // код, генерирующий исключение
}
catch (e: Exception) {
    // обработка исключения
}
finally {
    // постобработка
}

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

Блок finally является необязательным, его можно опустить. Блок catch также может отсутствовать, однако обязательно должен быть блок try и как минимум один из блоков: либо catch, либо finally.
Также конструкция может содержать несколько блоков catch для обработки каждого типа исключения, которое может возникнуть.

Блок catch выполняется, если только возникло исключение. Блок finally выполняется в любом случае, даже если нет исключения.

Например, при делении на ноль Kotlin генерирует исключение:

fun main() {

   try{
       val x : Int = 0
       val z : Int = 0 / x
       println("z = $z")
   }
   catch(e: Exception){
       println("Exception")
       println(e.message)
   }
}

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

Если необходимы какие-то завершающие действия, то можно добавить блок finally (например, если при работе с файлом возникает исключение, то в блоке finally
можно прописать закрытие файла):

try{
	val x : Int = 0
	val z : Int = 0 / x
	println("z = $z")
}
catch(e: Exception){
	println("Exception")
	println(e.message)
}
finally{
	println("Program has been finished")
}

В этом случае консольный вывод будет выглядеть следующим образом:

Exception
Program has been finished

Информация об исключении

Базовый класс исключений — класс Exception предоставляет ряд свойств, которые позволяют получить различную информацию об исключении:

  • message: сообщение об исключении

  • stackTrace: трассировка стека исключения — набор строк, где было сгенерировано исключение

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

Применение свойств:

fun main() {

    try{
        val x : Int = 0
        val z : Int = 0 / x
        println("z = $z")
    }
    catch(e: Exception){
        println(e.message)
        for(line in e.stackTrace) {
            println("at $line")
        }
    }
}

Консольный вывод программы:

/ by zero
at AppKt.main(app.kt:5)
at AppKt.main(app.kt)

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

Одна программа, один код может генерировать сразу несколько исключений. Для обработки каждого отдельного типа исключений можно определить отдельный блок catch.
Например, при одном исключении мы хотим производить одни действия, при другом — другие.

try {
	val nums = arrayOf(1, 2, 3, 4)
	println(nums[6])
}
catch(e:ArrayIndexOutOfBoundsException){
	println("Out of bound of array")
}
catch (e: Exception){
	println(e.message)
}

В данном случае при доступе по недействительному индексу в массиве будет генерироваться исключение типа ArrayIndexOutOfBoundsException. С
помощью блока catch(e:ArrayIndexOutOfBoundsException). Если в программе будут другие исключения, которые не представляют тип ArrayIndexOutOfBoundsException,
то они будут обрабатываться вторым блоком catch, так как Exception — это общий тип, который подходит под все типы исключений. При этом стоит отметить,
что в начале обрабатывается исключение более частного типа — ArrayIndexOutOfBoundsException, и только потом — более общего типа Exception.

Оператор throw

Возможно, в каких-то ситуациях мы вручную захотим генерировать исключение. Для генерации исключения применяется оператор
throw, после которого указывается объект исключения

Например, в функции проверки возраста мы можем генерировать исключение, если возраст не укладывается в некоторый диапазон:

fun main() {

    val checkedAge1 = checkAge(5)
    val checkedAge2 = checkAge(-115)
}
fun checkAge(age: Int): Int{
    if(age < 1 || age > 110) throw  Exception("Invalid value $age. Age must be greater than 0 and less than 110")
    println("Age $age is valid")
    return age
}

После оператора throw указан объект исключения. Для определения объекта Exception применяется конструктор, который принимает
в качестве параметра сообщение об исключении. В данном случае это сообщение о некорректности введенного значения.

И если при вызове функции checkAge() в нее будет передано число меньше 1 или больше 110, то будет сгенерировано исключение. Так, в данном случае
консольный вывод будет следующим:

Age 5 is valid
Exception in thread "main" java.lang.Exception: Invalid value -115. Age must be greater than 0 and less than 110
	at AppKt.checkAge(app.kt:7)
	at AppKt.main(app.kt:4)
	at AppKt.main(app.kt)

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

fun main() {
    try {
        val checkedAge1 = checkAge(5)
        val checkedAge2 = checkAge(-115)
    }
    catch (e: Exception){
        println(e.message)
    }
}
fun checkAge(age: Int): Int{
    if(age < 1 || age > 110) throw  Exception("Invalid value $age. Age must be greater than 0 and less than 110")
    println("Age $age is valid")
    return age
}

Возвращение значения

Конструкция try может возвращать значение. Например:

fun main() {
    val checkedAge1 = try { checkAge(5) } catch (e: Exception) { null }
    val checkedAge2 = try { checkAge(-125) } catch (e: Exception) { null }
    println(checkedAge1)    // 5
    println(checkedAge2)    // null
}
fun checkAge(age: Int): Int{
    if(age < 1 || age > 110) throw  Exception("Invalid value $age. Age must be greater than 0 and less than 110")
    println("Age $age is valid")
    return age
}

В данном случае переменная checkedAge1 получает результат функцию checkAge(). Если же произойдет исключение, тогда
переменная checkedAge1 получает то значение, которое указано в блоке catch, то есть в данном случае значение null.

При необрабходимости в блок catch можно добавить и другие выражения или возвратить другое значение:

fun main() {
    val checkedAge2 = try { checkAge(-125) } catch (e: Exception) { println(e.message); 18 }
    println(checkedAge2)
}
fun checkAge(age: Int): Int{
    if(age < 1 || age > 110) throw  Exception("Invalid value $age. Age must be greater than 0 and less than 110")
    println("Age $age is valid")
    return age
}

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

Improve Article

Save Article

Like Article

  • Read
  • Discuss
  • Improve Article

    Save Article

    Like Article

    Exceptions are the error which comes at the runtime and disrupts your flow of execution of the program. Basically exception is the unwanted event that occurs at runtime. The method by which we can handle this kind of error is called Exception Handling.

    Types of Exceptions

    1. Checked Exception: Exceptions that occur at the compile-time, or we can say that checked at the compile time is called Checked Exception. Example — IOException, Some file related Exception, etc
    2. Unchecked Exception: Exceptions that occur at the runtime is called Unchecked Exception. Example — OutofBound exception.

    Note: In Kotlin we have only unchecked Exceptions which only figure out at the runtime.

    How to Handle Exceptions?

    We have some keywords which help us to handle Exceptions.

    1. Try            // This will try to find the exception
    2. Throw      // If exception found then it will throw the exception
    3. Catch       // After throwing it will catch the exception and execute their body.

    We have one more keyword called Finally, It will always execute either we got exception or not. In this, we have some important codes that always need to execute.

    Note: If the program exit by exitProcess(Int) or abort(), then the finally will not be executed.

    How to Use try-catch and finally?

    try

    {

    }

    catch(ex : ExceptionName)

    {

    }

    finally

    {

    }

    Example 1: Divide By Zero 

    Kotlin

    fun main()

    {

        try

          {

          divide(10, 0

        }

        catch (ex : Exception)

        {

          println(ex.message) 

        }

    }

    fun divide(a : Int, b : Int)

    {

        if (b == 0)

            throw Exception("Divide by zero"

        println("Division is :" + a / b)

    }

    Output:

    Divide by zero
    

    Example 2: Let’s try the same code with try-catch and finally.

    Kotlin

    fun main()

    {

        try 

        {

          divide(20, 10

        }

        catch (ex : Exception)

        {

          println(ex.message) 

        }

        finally 

        {

          println("I'm executed")

        }

        try 

        {

          divide(10, 0

        }

        catch (ex : Exception)

        {

          println(ex.message) 

        }

        finally 

        {

          println("I'm executed")

        }

    }

    fun divide(a : Int, b : Int)

    {

        if (b == 0)

            throw Exception("Divide by zero"

        println("Division is :" + a / b)

    }

    Output:

    Division is : 2
    I'm executed
    Divide by zero
    I'm executed
    

    Note that In the above example, finally is executed in both cases either exception occurs or not.

    Last Updated :
    16 Aug, 2020

    Like Article

    Save Article

    An exception is an unwanted or unexpected event, which occurs during the execution of a program i.e at run time, that disrupts the normal flow of the program’s instructions. Exception handling is a technique, using which we can handle errors and prevent run time crashes that can stop our program.

    There are two types of Exceptions –

    1. Checked Exception – Exceptions that are typically set on methods and checked at the compile time, for example IOException, FileNotFoundException etc
    2. UnChecked Exception – Exceptions that are generally due to logical errors and checked at the run time, for example NullPointerException, ArrayIndexOutOfBoundException etc

    Kotlin Exceptions –

    In Kotlin, we have only unchecked exceptions and can be caught only at run time. All the exception classes are descendants of Throwable class.

    We generally use the throw-expression, to throw an exception object –

    throw Exception("Throw me")

    Some of the common exceptions are:

    • NullPointerException: It is thrown when we try to invoke a property or method on null object.
    • Arithmetic Exception: It is thrown when invalid arithmetic operations are performed on numbers. eg – divide by zero.
    • SecurityException: It is thrown to indicate security violation.
    • ArrayIndexOutOfBoundException: It is thrown when we try to access invalid index value of an array.

    Kotlin program of throwing arithmetic exception –

    Kotlin

    fun main(args : Array<String>){

        var num = 10 / 0     

        println(num)

    }

    Output:

    Exception in thread "main" java.lang.ArithmeticException: / by zero

    In the above program, we initialize the num variable with value 10/0, but we know in arithmetic divide by zero is not allowed. While we are trying to run the program it throws an exception.

    To solve this problem, we have to use try-catch block.

    Kotlin try-catch block –

    In Kotlin, we use try-catch block for exception handling in the program. The try block encloses the code which is responsible for throwing an exception and the catch block is used for handling the exception. This block must be written within the main or other methods. Try block should be followed by either catch block or finally block or both.

    Syntax for try-catch block –

    try {
       // code that can throw exception
    } catch(e: ExceptionName) {
       // catch the exception and handle it
    }
    

    Kotlin program of arithmetic exception handling using try-catch block –

    Kotlin

    import kotlin.ArithmeticException

    fun main(args : Array<String>){

        try{

            var num = 10 / 0

        }

        catch(e: ArithmeticException){

            println("Divide by zero not allowed")

        }

    }

    Output:

    Divide by zero not allowed

    Explanation:
    In the above program, we have used try-catch block. The num variable which can throw exception is enclosed within the braces of try block because divide by zero not defined in arithmetic. The exception caught by the catch block and execute the println() statement.

    Kotlin try-catch block as an expression –

    As we already know, expression always returns a value. We can use kotlin try-catch block as an expression in our program. The value returned by the expression will be either last expression of try block or last expression of catch block. If an exception occurs in the code, then catch block returns the value.

    Kotlin program of using try-catch as an expression –

    Kotlin

    fun test(a: Int, b: Int) : Any {

        return try {

            a/b

        }

        catch(e:Exception){

            println(e)

            "Divide by zero not allowed"

        }

    }

    fun main(args: Array<String>) {

        var result1 = test(10,2  )

        println(result1)

        var result = test(10,0 )  

        println(result)

    }

    Output:

    5
    java.lang.ArithmeticException: / by zero
    Divide by zero not allowed
    

    In the above code, we have used try-catch as an expression. Declare a function test on the top of program and it return a value using try-catch block. We have invoked the test function from main method and passed the parameter values (10,2) The test function evaluate the arguments and return try value (10/2 = 5). But in next call, we passed (b=0) and this time exception is caught and returns expression of catch block.

    Kotlin finally block –

    In Kotlin, finally block is always executes irrespective of whether an exception is handled or not by the catch block. So it is used to execute important code statement.
    We can also use finally block with try block and skip the catch block from there.

    Syntax of finally block with try block –

    try {
       // code that can throw exception
    } finally {
       // finally block code
    }
    

    Kotlin program of using finally block with try block block –

    Kotlin

    fun main(args : Array<String>){

        try{

            var ar = arrayOf(1,2,3,4,5)

            var int = ar[6]

            println(int)

        }

      finally {

            println("This block always executes")

        }

    }

    Output:

    This block always executes
    Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 5

    In the above program, we have used try with finally block and skipped the catch block. Here, exception is not handled by catch block but executes the finally block.

    Syntax of finally block with try-catch block –

    try {
       // code that can throw exception
    } catch(e: ExceptionName) {
       // catch the exception and handle it.
    } finally {
      // finally block code
    }
    

    We can also use try, catch and finally blocks all together.
    Kotlin program of using finally block with try-catch block-

    Kotlin

    fun main (args: Array<String>){  

        try {  

            var int = 10 / 0  

            println(int)  

        } catch (e: ArithmeticException) {  

            println(e)  

        } finally {  

            println("This block always executes")  

        }  

    }  

    Output:

    java.lang.ArithmeticException: / by zero
    This block always executes
    

    Kotlin throw keyword –

    In Kotlin, we use throw keyword to throw an explicit exception. It can also be used to throw a custom exception.

    Kotlin program of using throw keyword –

    Kotlin

    fun main(args: Array<String>) {

        test("abcd")

        println("executes after the validation")

    }

    fun test(password: String) {

        if (password.length < 6)

            throw ArithmeticException("Password is too short")

        else

            println("Strong password")

    }

    Output:

    Exception in thread "main" java.lang.ArithmeticException: Password is too short

    Last Updated :
    28 Mar, 2022

    Like Article

    Save Article

    Понравилась статья? Поделить с друзьями:
  • Обработка ошибок vba on error
  • Обработка ошибок json python
  • Обработка ошибок try except
  • Обработка ошибок java spring
  • Обработка ошибок swift