diff --git a/docs/topics/competitive-programming.md b/docs/topics/competitive-programming.md index f6cd1df19c3..52648c8620e 100644 --- a/docs/topics/competitive-programming.md +++ b/docs/topics/competitive-programming.md @@ -101,7 +101,7 @@ fun main() { ``` Note the use of Kotlin's -[null-assertion operator](null-safety.md#the-operator) `!!` +[null-assertion operator](null-safety.md#not-null-assertion-operator) `!!` after the [readLine()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/read-line.html) function call. Kotlin's `readLine()` function is defined to return a [nullable type](null-safety.md#nullable-types-and-non-nullable-types) diff --git a/docs/topics/jvm/java-to-kotlin-nullability-guide.md b/docs/topics/jvm/java-to-kotlin-nullability-guide.md index d69894cb0a0..f555ad79937 100644 --- a/docs/topics/jvm/java-to-kotlin-nullability-guide.md +++ b/docs/topics/jvm/java-to-kotlin-nullability-guide.md @@ -85,7 +85,7 @@ After the check is passed successfully, the compiler treats the variable as if i in the scope where the compiler performs the check. If you don't perform this check, the code will fail to compile with the following message: -"Only [safe (?.)](null-safety.md#safe-calls) or [non-nullable asserted (!!.) calls](null-safety.md#the-operator) are allowed +"Only [safe (?.)](null-safety.md#safe-call-operator) or [non-nullable asserted (!!.) calls](null-safety.md#not-null-assertion-operator) are allowed on a [nullable receiver](extensions.md#nullable-receiver) of type String?". You can write the same shorter – use the [safe-call operator ?. (If-not-null shorthand)](idioms.md#if-not-null-shorthand), diff --git a/docs/topics/keyword-reference.md b/docs/topics/keyword-reference.md index 625dbd9e62c..1b610799969 100644 --- a/docs/topics/keyword-reference.md +++ b/docs/topics/keyword-reference.md @@ -143,8 +143,8 @@ Kotlin supports the following operators and special symbols: * `===`, `!==` - [referential equality operators](equality.md#referential-equality). * `<`, `>`, `<=`, `>=` - [comparison operators](operator-overloading.md#comparison-operators) (translated to calls of `compareTo()` for non-primitive types). * `[`, `]` - [indexed access operator](operator-overloading.md#indexed-access-operator) (translated to calls of `get` and `set`). - * `!!` [asserts that an expression is non-nullable](null-safety.md#the-operator). - * `?.` performs a [safe call](null-safety.md#safe-calls) (calls a method or accesses a property if the receiver is non-nullable). + * `!!` [asserts that an expression is non-nullable](null-safety.md#not-null-assertion-operator). + * `?.` performs a [safe call](null-safety.md#safe-call-operator) (calls a method or accesses a property if the receiver is non-nullable). * `?:` takes the right-hand value if the left-hand value is null (the [elvis operator](null-safety.md#elvis-operator)). * `::` creates a [member reference](reflection.md#function-references) or a [class reference](reflection.md#class-references). * `..`, `..<` create [ranges](ranges.md). diff --git a/docs/topics/null-safety.md b/docs/topics/null-safety.md index cd55ae25cee..42e682fee04 100644 --- a/docs/topics/null-safety.md +++ b/docs/topics/null-safety.md @@ -1,229 +1,453 @@ [//]: # (title: Null safety) -## Nullable types and non-nullable types - -Kotlin's type system is aimed at eliminating the danger of null references, also known as [The Billion Dollar Mistake](https://en.wikipedia.org/wiki/Null_pointer#History). +Null safety is a Kotlin feature designed to significantly reduce the risk of null references, also known as [The Billion-Dollar Mistake](https://en.wikipedia.org/wiki/Null_pointer#History). 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`, +reference results in a null reference exception. In Java, this would be the equivalent of a `NullPointerException`, or an _NPE_ for short. +Kotlin explicitly supports nullability as part of its type system, meaning you can explicitly declare +which variables or properties are allowed to be `null`. Also, when you declare non-null variables, the compiler +enforces that these variables cannot hold a `null` value, +preventing an NPE. + +Kotlin's null safety ensures safer code by catching potential null-related issues at compile time rather than runtime. +This feature improves code robustness, readability, and maintainability by explicitly expressing `null` values, making the code easier to understand and manage. + 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](inheritance.md#derived-class-initialization-order) whose implementation - in the derived class uses an uninitialized state. +* An explicit call to [`throw NullPointerException()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-null-pointer-exception/). +* Usage of the [not-null assertion operator `!!`](#not-null-assertion-operator). +* Data inconsistency during initialization, such as when: + * An uninitialized `this` available in a constructor is used somewhere else ([a "leaking `this`"](https://youtrack.jetbrains.com/issue/KTIJ-9751)). + * A [superclass constructor calling an open member](inheritance.md#derived-class-initialization-order) whose implementation + in the derived class uses an uninitialized state. * Java interoperation: - * Attempts to access a member of a `null` reference of a [platform type](java-interop.md#null-safety-and-platform-types); - * 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. + * Attempts to access a member of a `null` reference of a [platform type](java-interop.md#null-safety-and-platform-types). + * Nullability issues with generic types. For example, a piece of Java code adding + `null` into a Kotlin `MutableList`, which would require `MutableList` to handle it properly. * 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-nullable references). -For example, a regular variable of type `String` cannot hold `null`: +> Besides NPE, another exception related to null safety is [`UninitializedPropertyAccessException`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-uninitialized-property-access-exception/). Kotlin throws this exception +> when you try to access a property that has not been initialized, ensuring that non-nullable properties are not used until they are ready. +> This typically happens with [`lateinit` properties](properties.md#late-initialized-properties-and-variables). +> +{type="tip"} + +## Nullable types and non-nullable types + +In Kotlin, the type system distinguishes between types that can hold `null` (nullable types) and those that +cannot (non-nullable types). For example, a regular variable of type `String` cannot hold `null`: ```kotlin fun main() { //sampleStart - var a: String = "abc" // Regular initialization means non-nullable by default - a = null // compilation error + // Assigns a non-null string to a variable + var a: String = "abc" + // Attempts to re-assign null to the non-nullable variable + a = null + print(a) + // Null can not be a value of a non-null type String //sampleEnd } ``` {kotlin-runnable="true" validate="false"} -To allow nulls, you can declare a variable as a nullable string by writing `String?`: +You can safely call a method or access a property on `a`. It's guaranteed not to cause an NPE because `a` is a non-nullable variable. +The compiler ensures that `a` always holds a valid `String` value, so there's no risk of accessing its properties or methods when it's `null`: ```kotlin fun main() { //sampleStart - var b: String? = "abc" // can be set to null - b = null // ok - print(b) + // Assigns a non-null string to a variable + val a: String = "abc" + // Returns the length of a non-nullable variable + val l = a.length + print(l) + // 3 //sampleEnd } ``` -{kotlin-runnable="true"} +{kotlin-runnable="true" validate="false"} -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: +To allow `null` values, declare a variable with a `?` sign right after the variable type. For example, +you can declare a nullable string by writing `String?`. This expression makes `String` a type that +can accept `null`: ```kotlin -val l = a.length +fun main() { +//sampleStart + // Assigns a nullable string to a variable + var b: String? = "abc" + // Successfully re-assigns null to the nullable variable + b = null + print(b) + // null +//sampleEnd +} ``` +{kotlin-runnable="true"} -But if you want to access the same property on `b`, that would not be safe, and the compiler reports an error: +If you try accessing `length` directly on `b`, the compiler reports an error. This is because `b` is declared as a nullable +variable and can hold `null` values. Attempting to access properties on nullables directly leads to an NPE: ```kotlin -val l = b.length // error: variable 'b' can be null +fun main() { +//sampleStart + // Assigns a nullable string to a variable + var b: String? = "abc" + // Re-assigns null to the nullable variable + b = null + // Tries to directly return the length of a nullable variable + val l = b.length + print(l) + // Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String? +//sampleEnd +} ``` +{kotlin-runnable="true" kotlin-min-compiler-version="2.0" validate="false"} + +In the example above, the compiler requires you to use safe calls to check for nullability before accessing properties or +performing operations. There are several ways to handle nullables: + +* [Check for `null` with the `if` conditional](#check-for-null-with-the-if-conditional) +* [Safe call operator `?.`](#safe-call-operator) +* [Elvis operator `?:`](#elvis-operator) +* [Not-null assertion operator `!!`](#not-null-assertion-operator) +* [Nullable receiver](#nullable-receiver) +* [`let` function](#let-function) +* [Safe casts `as?`](#safe-casts) +* [Collections of a nullable type](#collections-of-a-nullable-type) + +Read the next sections for details and examples of `null` handling tools and techniques. -But you still need to access that property, right? There are a few ways to do so. +## Check for `null` with the `if` conditional -## Checking for `null` in conditions +When working with nullable types, you need to handle nullability safely to avoid an NPE. One way to +handle this is checking for nullability explicitly with the `if` conditional expression. -First, you can explicitly check whether `b` is `null`, and handle the two options separately: +For example, check whether `b` is `null` and then access `b.length`: ```kotlin -val l = if (b != null) b.length else -1 +fun main() { +//sampleStart + // Assigns null to a nullable variable + val b: String? = null + // Checks for nullability first and then accesses length + val l = if (b != null) b.length else -1 + print(l) + // -1 +//sampleEnd +} ``` +{kotlin-runnable="true"} + +In the example above, the compiler performs a [smart cast](typecasts.md#smart-casts) to change the type from nullable `String?` to non-nullable `String`. It also tracks the information about +the check you performed and allows the call to `length` inside the `if` conditional. -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: ```kotlin fun main() { //sampleStart + // Assigns a nullable string to a variable val b: String? = "Kotlin" + + // Checks for nullability first and then accesses length if (b != null && b.length > 0) { print("String of length ${b.length}") + // Provides alternative if the condition is not met } else { print("Empty string") + // String of length 6 } //sampleEnd } ``` {kotlin-runnable="true"} -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. +Note that the example above only works when the compiler can guarantee that `b` doesn't change between the check and its usage, same as +the [smart cast prerequisites](typecasts.md#smart-cast-prerequisites). -## Safe calls +## Safe call operator -Your second option for accessing a property on a nullable variable is using the safe call operator `?.`: +The safe call operator `?.` allows you to handle nullability safely in a shorter form. Instead of throwing an NPE, +if the object is `null`, the `?.` operator simply returns `null`: ```kotlin fun main() { //sampleStart - val a = "Kotlin" + // Assigns a nullable string to a variable + val a: String? = "Kotlin" + // Assigns null to a nullable variable val b: String? = null + + // Checks for nullability and returns length or null + println(a?.length) + // 6 println(b?.length) - println(a?.length) // Unnecessary safe call + // null //sampleEnd } ``` {kotlin-runnable="true"} -This returns `b.length` if `b` is not null, and `null` otherwise. The type of this expression is `Int?`. +The `b?.length` expression checks for nullability and returns `b.length` if `b` is non-null, or `null` otherwise. The type of this expression is `Int?`. + +You can use the `?.` operator with both [`var` and `val` variables](basic-syntax.md#variables) in Kotlin: + +* A nullable `var` can hold a `null` (for example, `var nullableValue: String? = null`) or a non-null value (for example, `var nullableValue: String? = "Kotlin"`). If it's a non-null value, you can change it to `null` at any point. +* A nullable `val` can hold a `null` (for example, `val nullableValue: String? = null`) or a non-null value (for example, `val nullableValue: String? = "Kotlin"`). If it's a non-null value, you cannot change it to `null` subsequently. 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), +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: ```kotlin bob?.department?.head?.name ``` -Such a chain returns `null` if any of the properties in it is `null`. +This chain returns `null` if any of its properties are `null`. Here's the equivalent of the same safe call but with the `if` conditional: + +```kotlin +if (person != null && person.department != null) { + person.department.head = managersPool.getManager() +} +``` + +You can also place a safe call on the left side of an assignment: + +```kotlin +person?.department?.head = managersPool.getManager() +``` + +In the example above, if one of the receivers in the safe call chain is `null`, the assignment is skipped, and the expression on the right is not evaluated at all. For example, if either +`person` or `person.department` is `null`, the function is not called. + +## Elvis operator -To perform a certain operation only for non-null values, you can use the safe call operator together with -[`let`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/let.html): +When working with nullable types, you can check for `null` and provide an alternative value. For example, if `b` is not `null`, +access `b.length`. Otherwise, return an alternative value: ```kotlin fun main() { //sampleStart - val listWithNulls: List = listOf("Kotlin", null) - for (item in listWithNulls) { - item?.let { println(it) } // prints Kotlin and ignores null - } + // Assigns null to a nullable variable + val b: String? = null + // Checks for nullability. If not null, returns length. If null, returns 0 + val l: Int = if (b != null) b.length else 0 + println(l) + // 0 //sampleEnd } ``` {kotlin-runnable="true"} -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: +Instead of writing the complete `if` expression, you can handle this in a more concise way with the Elvis operator `?:`: ```kotlin -// If either `person` or `person.department` is null, the function is not called: -person?.department?.head = managersPool.getManager() +fun main() { +//sampleStart + // Assigns null to a nullable variable + val b: String? = null + // Checks for nullability. If not null, returns length. If null, returns a non-null value + val l = b?.length ?: 0 + println(l) + // 0 +//sampleEnd +} ``` +{kotlin-runnable="true"} -## Nullable receiver - -Extension functions can be defined on a [nullable receiver](extensions.md#nullable-receiver). -This way you can specify behaviour for null values without the need to use null-checking logic at each call-site. +If the expression to the left of `?:` is not `null`, the Elvis operator returns it. Otherwise, the Elvis operator returns the expression +to the right. The expression on the right-hand side is evaluated only if the left-hand side is `null`. -For example, the [`toString()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/to-string.html) 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: +Since `throw` and `return` are expressions in Kotlin, you can also use them on +the right-hand side of the Elvis operator. This can be handy, for example, when checking function arguments: ```kotlin -val person: Person? = null -logger.debug(person.toString()) // Logs "null", does not throw an exception +fun foo(node: Node): String? { + // Checks for getParent(). If not null, it's assigned to parent. If null, returns null + val parent = node.getParent() ?: return null + // Checks for getName(). If not null, it's assigned to name. If null, throws exception + val name = node.getName() ?: throw IllegalArgumentException("name expected") + // ... +} ``` -If you want your `toString()` invocation to return a nullable string, use the [safe-call operator `?.`](#safe-calls): +## Not-null assertion operator + +The not-null assertion operator `!!` converts any value to a non-nullable type. + +When you apply the `!!` operator to a variable whose value is not `null`, it's safely handled as a non-nullable type, +and the code executes normally. However, if the value is `null`, the `!!` operator forces it to be treated as non-nullable, +which results in an NPE. + +When `b` is not `null` and the `!!` operator makes it return its non-null value (which is a `String` in this example), it accesses `length` correctly: ```kotlin -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` +fun main() { +//sampleStart + // Assigns a nullable string to a variable + val b: String? = "Kotlin" + // Treats b as non-null and accesses its length + val l = b!!.length + println(l) + // 6 +//sampleEnd } ``` +{kotlin-runnable="true"} -## 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": +When `b` is `null` and the `!!` operator makes it return its non-null value, and an NPE occurs: ```kotlin -val l: Int = if (b != null) b.length else -1 +fun main() { +//sampleStart + // Assigns null to a nullable variable + val b: String? = null + // Treats b as non-null and tries to access its length + val l = b!!.length + println(l) + // Exception in thread "main" java.lang.NullPointerException +//sampleEnd +} ``` +{kotlin-runnable="true" kotlin-min-compiler-version="2.0" validate="false"} + +The `!!` operator is particularly useful +when you are confident that a value is not `null` and there’s no chance of getting an NPE, but the compiler cannot guarantee this due to certain rules. +In such cases, you can use the `!!` operator to explicitly tell the compiler that the value is not `null`. + +## Nullable receiver + +You can use extension functions with a [nullable receiver type](extensions.md#nullable-receiver), +allowing these functions to be called on variables that might be `null`. -Instead of writing the complete `if` expression, you can also express this with the Elvis operator `?:`: +By defining an extension function on a nullable receiver type, you can handle `null` values within the function itself +instead of checking for `null` at every place where you call the function. + +For example, the [`.toString()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/to-string.html) extension function +can be called on a nullable receiver. When invoked on a `null` value, it safely returns the string `"null"` without throwing an exception: ```kotlin -val l = b?.length ?: -1 +// SampleStart +fun main() { + // Assigns null to a nullable Person object stored in the person variable + val person: Person? = null + + // Applies .toString to the nullable person variable and prints a string + println(person.toString()) + // null +} + +// Defines a simple Person class +data class Person(val name: String) +// SampleEnd ``` +{kotlin-runnable="true"} -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`. +In the example above, even though `person` is `null`, the `.toString()` function safely returns the string `"null"`. This can be helpful for debugging and logging. -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: +If you expect the `.toString()` function to return a nullable string (either a string representation or `null`), use the [safe-call operator `?.`](#safe-call-operator). +The `?.` operator calls `.toString()` only if the object is not `null`, otherwise it returns `null`: ```kotlin -fun foo(node: Node): String? { - val parent = node.getParent() ?: return null - val name = node.getName() ?: throw IllegalArgumentException("name expected") - // ... +// SampleStart +fun main() { + // Assigns a nullable Person object to a variable + val person1: Person? = null + val person2: Person? = Person("Alice") + + // Prints "null" if person is null; otherwise prints the result of person.toString() + println(person1?.toString()) + // null + println(person2?.toString()) + // Person(name=Alice) } + +// Defines a Person class +data class Person(val name: String) +// SampleEnd ``` +{kotlin-runnable="true"} + +The `?.` operator allows you to safely handle potential `null` values while still accessing properties or functions of objects that might be `null`. -## The `!!` operator +## Let function -The third option is for NPE-lovers: the not-null assertion operator (`!!`) converts any value to a non-nullable -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`: +To handle `null` values and perform operations only on non-null types, you can use the safe call operator `?.` together with the +[`let` function](scope-functions.md#let). + +This combination is useful for evaluating an expression, check the result for `null`, and execute code only if it's not `null`, avoiding manual null checks: ```kotlin -val l = b!!.length -``` +fun main() { +//sampleStart + // Declares a list of nullable strings + val listWithNulls: List = listOf("Kotlin", 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. + // Iterates over each item in the list + for (item in listWithNulls) { + // Checks if the item is null and only prints non-null values + item?.let { println(it) } + //Kotlin + } +//sampleEnd +} +``` +{kotlin-runnable="true"} ## 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: +The regular Kotlin operator for [type casts](typecasts.md#unsafe-cast-operator) is the `as` operator. However, regular casts can result in an exception +if the object is not of the target type. + +You can use the `as?` operator for safe casts. It tries to cast a value to the specified type and returns `null` if the value is not of that type: ```kotlin -val aInt: Int? = a as? Int +fun main() { +// SampleStart + // Declares a variable of type Any, which can hold any type of value + val a: Any = "Hello, Kotlin!" + + // Safe casts to Int using the 'as?' operator + val aInt: Int? = a as? Int + // Safe casts to String using the 'as?' operator + val aString: String? = a as? String + + println(aInt) + // null + println(aString) + // "Hello, Kotlin!" +// SampleEnd +} ``` +{kotlin-runnable="true"} + +The code above prints `null` because `a` is not an `Int`, so the cast fails safely. It also prints +`"Hello, Kotlin!"` because it matches the `String?` type, so the safe cast succeeds. ## Collections of a nullable type -If you have a collection of elements of a nullable type and want to filter non-nullable elements, you can do so by using -`filterNotNull`: +If you have a collection of nullable elements and want to keep only the non-null ones, use +the `filterNotNull()` function: ```kotlin -val nullableList: List = listOf(1, 2, null, 4) -val intList: List = nullableList.filterNotNull() +fun main() { + // SampleStart + // Declares a list containing some null and non-null integer values + val nullableList: List = listOf(1, 2, null, 4) + + // Filters out null values, resulting in a list of non-null integers + val intList: List = nullableList.filterNotNull() + + println(intList) + // [1, 2, 4] + // SampleEnd +} ``` +{kotlin-runnable="true"} ## What's next? diff --git a/docs/topics/scope-functions.md b/docs/topics/scope-functions.md index ce6b7e32e9f..f875f794ede 100644 --- a/docs/topics/scope-functions.md +++ b/docs/topics/scope-functions.md @@ -348,7 +348,7 @@ fun main() { {kotlin-runnable="true" kotlin-min-compiler-version="1.3"} `let` is often used to execute a code block containing non-null values. To perform actions on a non-null object, use -the [safe call operator `?.`](null-safety.md#safe-calls) on it and call `let` with the actions in its lambda. +the [safe call operator `?.`](null-safety.md#safe-call-operator) on it and call `let` with the actions in its lambda. ```kotlin fun processNonNullString(str: String) {}