- Исключения
- Try — это выражение
- Проверяемые исключения
- Тип Nothing
- Совместимость с Java
- Null safety
- Checking for null in conditions
- Safe calls
- Nullable receiver
- Elvis operator
- The !! operator
- Safe casts
- Collections of a nullable type
- Exceptions
- Try is an expression
- Checked exceptions
- The Nothing type
- Java interoperability
Исключения
Все исключения в Kotlin являются наследниками класса Throwable . У каждого исключения есть сообщение, трассировка стека и (опционально) причина, по которой это исключение вероятно было вызвано.
Для того чтобы возбудить исключение явным образом, используйте оператор throw .
Чтобы перехватить исключение, используйте выражение try . catch .
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)
Возвращаемым значением будет либо последнее выражение в блоке try , либо последнее выражение в блоке catch (или блоках). Содержимое finally блока никак не повлияет на результат try -выражения.
Проверяемые исключения
В Kotlin нет проверяемых исключений. Для этого существует целый ряд причин, но мы рассмотрим простой пример, который иллюстрирует причину этого.
Приведённый ниже фрагмент кода является примером простого интерфейса в JDK, который реализован в классе StringBuilder .
Appendable append(CharSequence csq) throws IOException;
Сигнатура говорит, что каждый раз, когда я присоединяю строку к чему-то (к StringBuilder , какому-нибудь логу, сообщению в консоль и т.п), мне необходимо отлавливать исключения типа IOExceptions . Почему? Потому, что данная операция может вызывать IO (Input-Output: Ввод-Вывод) ( Writer также реализует интерфейс Appendable ). Данный факт постоянно приводит к написанию подобного кода:
И это плохо. См. Effective Java, Item 77: Don’t ignore exceptions (не игнорируйте исключения).
Брюс Эккель как-то сказал о проверяемых исключения:
Examination of small programs leads to the conclusion that requiring exception specifications >could both enhance developer productivity and enhance code quality, but experience with large software projects suggests >a different result – decreased productivity and little or no increase in code quality. —>
Анализ небольших программ показал, что обязательная обработка исключений может повысить производительность разработчика и улучшить качество кода. Однако, изучение крупных проектов по разработке программного обеспечения позволяет сделать противоположный вывод: происходит понижение продуктивности и сравнительно небольшое улучшение кода (а иногда и без всякого улучшения).
Вот несколько других рассуждений по этому поводу:
Если вы хотите предупредить вызывающие объекты о возможных исключениях при вызове 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
При вызове такой функции компилятор будет в курсе, что исполнения кода далее не последует:
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
Совместимость с Java
См. раздел, посвящённый исключениям, Страница совместимости Java для получения информации о совместимости с Java.
Null safety
Kotlin’s type system is aimed at eliminating the danger of null references, also known as The Billion Dollar Mistake.
One of the most common pitfalls in many programming languages, including Java, is that accessing a member of a null reference will result in a null reference exception. In Java this would be the equivalent of a NullPointerException , or an NPE for short.
The only possible causes of an NPE in Kotlin are:
- An explicit call to throw NullPointerException() .
- Usage of the !! operator that is described below.
- Data inconsistency with regard to initialization, such as when:
- An uninitialized this available in a constructor is passed and used somewhere (a «leaking this «).
- A superclass constructor calls an open member whose implementation in the derived class uses an uninitialized state.
- Attempts to access a member of a null reference of a platform type;
- Nullability issues with generic types being used for Java interoperation. For example, a piece of Java code might add null into a Kotlin MutableList , therefore requiring a MutableList for working with it.
- Other issues caused by external Java code.
In Kotlin, the type system distinguishes between references that can hold null (nullable references) and those that cannot (non-null references). For example, a regular variable of type String cannot hold null :
To allow nulls, you can declare a variable as a nullable string by writing String? :
Now, if you call a method or access a property on a , it’s guaranteed not to cause an NPE, so you can safely say:
But if you want to access the same property on b , that would not be safe, and the compiler reports an error:
But you still need to access that property, right? There are a few ways to do so.
Checking for null in conditions
First, you can explicitly check whether b is null , and handle the two options separately:
The compiler tracks the information about the check you performed, and allows the call to length inside the if . More complex conditions are supported as well:
Note that this only works where b is immutable (meaning it is a local variable that is not modified between the check and its usage or it is a member val that has a backing field and is not overridable), because otherwise it could be the case that b changes to null after the check.
Safe calls
Your second option for accessing a property on a nullable variable is using the safe call operator ?. :
This returns b.length if b is not null, and null otherwise. The type of this expression is Int? .
Safe calls are useful in chains. For example, Bob is an employee who may be assigned to a department (or not). That department may in turn have another employee as a department head. To obtain the name of Bob’s department head (if there is one), you write the following:
Such a chain returns null if any of the properties in it is null .
To perform a certain operation only for non-null values, you can use the safe call operator together with let :
fun main() < //sampleStart val listWithNulls: List= listOf("Kotlin", null) for (item in listWithNulls) < item?.let < println(it) >// prints Kotlin and ignores null > //sampleEnd >
A safe call can also be placed on the left side of an assignment. Then, if one of the receivers in the safe calls chain is null , the assignment is skipped and the expression on the right is not evaluated at all:
// If either `person` or `person.department` is null, the function is not called: person?.department?.head = managersPool.getManager()
Nullable receiver
Extension functions can be defined on a nullable receiver. This way you can specify behaviour for null values without the need to use null-checking logic at each call-site.
For example, the toString() function is defined on a nullable receiver. It returns the String «null» (as opposed to a null value). This can be helpful in certain situations, for example, logging:
val person: Person? = null logger.debug(person.toString()) // Logs «null», does not throw an exception
If you want your toString() invocation to return a nullable string, use the safe-call operator ?. :
var timestamp: Instant? = null val isoTimestamp = timestamp?.toString() // Returns a String? object which is `null` if (isoTimestamp == null) < // Handle the case where timestamp was `null` >
Elvis operator
When you have a nullable reference, b , you can say «if b is not null , use it, otherwise use some non-null value»:
Instead of writing the complete if expression, you can also express this with the Elvis operator ?: :
If the expression to the left of ?: is not null , the Elvis operator returns it, otherwise it returns the expression to the right. Note that the expression on the right-hand side is evaluated only if the left-hand side is null .
Since throw and return are expressions in Kotlin, they can also be used on the right-hand side of the Elvis operator. This can be handy, for example, when checking function arguments:
The !! operator
The third option is for NPE-lovers: the not-null assertion operator ( !! ) converts any value to a non-null type and throws an exception if the value is null . You can write b!! , and this will return a non-null value of b (for example, a String in our example) or throw an NPE if b is null :
Thus, if you want an NPE, you can have it, but you have to ask for it explicitly and it won’t appear out of the blue.
Safe casts
Regular casts may result in a ClassCastException if the object is not of the target type. Another option is to use safe casts that return null if the attempt was not successful:
Collections of a nullable type
If you have a collection of elements of a nullable type and want to filter non-null elements, you can do so by using filterNotNull :
Exceptions
All exception classes in Kotlin inherit the Throwable class. Every exception has a message, a stack trace, and an optional cause.
To throw an exception object, use the throw expression:
To catch an exception, use the try . catch expression:
There may be zero or more catch blocks, and the finally block may be omitted. However, at least one catch or finally block is required.
Try is an expression
try is an expression, which means it can have a return value:
The returned value of a try expression is either the last expression in the try block or the last expression in the catch block (or blocks). The contents of the finally block don’t affect the result of the expression.
Checked exceptions
Kotlin does not have checked exceptions. There are many reasons for this, but we will provide a simple example that illustrates why it is the case.
The following is an example interface from the JDK implemented by the StringBuilder class:
This signature says that every time I append a string to something (a StringBuilder , some kind of a log, a console, etc.), I have to catch the IOExceptions . Why? Because the implementation might be performing IO operations ( Writer also implements Appendable ). The result is code like this all over the place:
And that’s not good. Just take a look at Effective Java, 3rd Edition, Item 77: Don’t ignore exceptions.
Bruce Eckel says this about checked exceptions:
Examination of small programs leads to the conclusion that requiring exception specifications could both enhance developer productivity and enhance code quality, but experience with large software projects suggests a different result – decreased productivity and little or no increase in code quality.
And here are some additional thoughts on the matter:
If you want to alert callers about possible exceptions when calling Kotlin code from Java, Swift, or Objective-C, you can use the @Throws annotation. Read more about using this annotation for Java and for Swift and Objective-C.
The Nothing type
throw is an expression in Kotlin, so you can use it, for example, as part of an Elvis expression:
The throw expression has the type Nothing . This type has no values and is used to mark code locations that can never be reached. In your own code, you can use Nothing to mark a function that never returns:
When you call this function, the compiler will know that the execution doesn’t continue beyond the call:
val s = person.name ?: fail(«Name required») println(s) // ‘s’ is known to be initialized at this point
You may also encounter this type when dealing with type inference. The nullable variant of this type, Nothing? , has exactly one possible value, which is null . If you use null to initialize a value of an inferred type and there’s no other information that can be used to determine a more specific type, the compiler will infer the Nothing? type:
Java interoperability
Please see the section on exceptions in the Java interoperability page for information about Java interoperability.