Null Safety
## Learning Objectives
- Understand Kotlin's null safety system
- Use safe call operator (?.)
- Work with the Elvis operator (?:)
- Use the not-null assertion (!!)
- Learn about safe casts (as?)
## Nullable Types
Kotlin's type system distinguishes between nullable and non-nullable types:
```kotlin
fun main() {
// Non-nullable - cannot hold null
val name: String = "Kotlin"
// name = null // Error!
// Nullable - can hold null
var nullableName: String? = "Kotlin"
nullableName = null // OK
println(nullableName)
}
```
## Safe Call Operator (?.)
The safe call operator returns null if the object is null:
```kotlin
fun main() {
val str: String? = "Hello"
// Safe call - returns length or null
val length1 = str?.length
println("Length 1: $length1") // 5
str = null
val length2 = str?.length
println("Length 2: $length2") // null
}
```
### Chaining Safe Calls
```kotlin
fun main() {
val person: Person? = Person(Address("New York"))
// Chained safe calls
val city = person?.address?.city
println("City: $city") // New York (or null if any part is null)
person?.address = null
val city2 = person?.address?.city
println("City 2: $city2") // null
}
class Person(var address: Address?)
class Address(var city: String?)
```
## Elvis Operator (?:)
The Elvis operator provides a default value when null:
```kotlin
fun main() {
val str: String? = null
// Without Elvis - returns null
val length1 = str?.length
println("Length: $length1") // null
// With Elvis - returns default
val length2 = str?.length ?: 0
println("Length: $length2") // 0
}
```
### Elvis with throw
```kotlin
fun main() {
val str: String? = null
// Elvis can throw exceptions too
val nonNull = str ?: throw IllegalArgumentException("String cannot be null")
}
fun getName(person: Person?): String {
return person?.name ?: "Unknown"
}
```
## Not-Null Assertion (!!)
Use `!!` when you're certain a value is not null:
```kotlin
fun main() {
val str: String? = "Hello"
// !! asserts value is not null
val length = str!!.length
println("Length: $length") // 5
// If null, throws NullPointerException
val nullStr: String? = null
// nullStr!!.length // Throws NullPointerException
}
```
### When to Use
- Only when you're absolutely certain the value is not null
- Avoid using `!!` in production code
- It defeats the purpose of Kotlin's null safety
## Safe Cast (as?)
Safe cast returns null instead of throwing ClassCastException:
```kotlin
fun main() {
val obj: Any = "Hello"
// Regular cast - throws if fails
// val str: String = obj as String
// Safe cast - returns null if fails
val str: String? = obj as? String
println("Safe cast: $str") // Hello
val num: Int? = obj as? Int
println("Safe cast to Int: $num") // null
}
```
## Let Function
Use `let` to execute code only if value is not null:
```kotlin
fun main() {
val str: String? = "Hello"
// execute only if not null
str?.let {
println("Length: ${it.length}")
}
// with default behavior
str?.let { it.length } ?: println("String is null")
}
```
### Let with Multiple Null Checks
```kotlin
fun main() {
val firstName: String? = "John"
val lastName: String? = "Doe"
// Combining multiple lets
firstName?.let { f ->
lastName?.let { l ->
println("$f $l")
}
}
}
```
## The ? Operator Summary
| Operator | Description | Returns |
|----------|-------------|---------|
| `?.` | Safe call | `null` if receiver is `null` |
| `?:` | Elvis operator | Default if left is `null` |
| `!!` | Not-null assertion | Throws NPE if `null` |
| `as?` | Safe cast | `null` if cast fails |
## Null Safety with Collections
```kotlin
fun main() {
val list: List? = listOf("a", "b", "c")
// Safe call with size
println(list?.size) // 3
list = null
// Empty list if null
val size = list?.size ?: 0
println(size) // 0
// Null list
val emptyList = list?.filter { it.isNotEmpty() } ?: emptyList()
println(emptyList) // []
}
```
## Platform Types
When Java code is used from Kotlin, types from Java can be platform types (can be null or non-null):
```kotlin
// Java code
public class JavaExample {
public String getGreeting() {
return "Hello";
}
}
```
```kotlin
// Kotlin code calling Java
fun main() {
val javaExample = JavaExample()
val greeting: String = javaExample.getGreeting() // Non-null
// If Java method returns @Nullable String
// Then Kotlin sees it as String?
}
```
## Checking for Null in Conditions
```kotlin
fun main() {
val str: String? = "Hello"
// Regular null check
if (str != null) {
println(str.length) // Smart cast to String
}
// After null check, smart cast applies
// Works only with local variables
}
```
### Note on Smart Casts
Smart casts don't work with mutable variables or properties that could be changed:
```kotlin
fun main() {
var str: String? = "Hello"
// Smart cast doesn't work because var can be changed
// str could be changed to null between check and use
if (str != null) {
// println(str.length) // Error: only safe call allowed
}
// Safe call works
println(str?.length)
}
```
## Summary
- Kotlin distinguishes nullable (`T?`) and non-nullable (`T`) types
- `?.` safe call returns null if receiver is null
- `?:` Elvis operator provides default value
- `!!` asserts non-null, throws NPE if wrong
- `as?` safe cast returns null on failure
- `?.let { }` executes block only if not null
- Prefer safe calls over `!!` in production code
- Smart casts work with immutable local variables
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →