Null Safety
Null Safety
The Null Problemโ
In many languages, accessing a null reference causes a NullPointerException (NPE). Kotlin eliminates this by distinguishing between nullable and non-nullable types at compile time.
Non-Nullable Types (Default)โ
fun main() {
var name: String = "Alice"
// name = null // COMPILE ERROR โ String cannot hold null
println(name.length) // Always safe
}
Nullable Types (?)โ
Add ? after the type to allow null:
fun main() {
var name: String? = "Alice"
name = null // OK โ String? can hold null
// println(name.length) // COMPILE ERROR โ unsafe
}
Safe Call Operator (?.)โ
Returns null instead of throwing NPE:
fun main() {
val name: String? = null
println(name?.length) // null โ no crash
val name2: String? = "Kotlin"
println(name2?.length) // 6
}
Elvis Operator (?:)โ
Provides a default value when the expression is null:
fun main() {
val name: String? = null
val display = name ?: "Unknown"
println(display) // Unknown
val length = name?.length ?: 0
println(length) // 0
}
Not-Null Assertion (!!)โ
Forces access โ throws NPE if null. Use with caution!
fun main() {
val name: String? = "Kotlin"
println(name!!.length) // 6 โ works if not null
val empty: String? = null
// println(empty!!.length) // Throws NullPointerException!
}
Safe Cast (as?)โ
Returns null instead of throwing ClassCastException:
fun main() {
val obj: Any = "Hello"
val str: String? = obj as? String
val num: Int? = obj as? Int
println(str) // Hello
println(num) // null โ not an Int, no crash
}
Chaining Safe Callsโ
data class Address(val city: String?)
data class Person(val name: String, val address: Address?)
fun main() {
val person: Person? = Person("Alice", Address(null))
val city = person?.address?.city ?: "No city"
println(city) // No city
}
let with Nullableโ
Executes a block only if the value is non-null:
fun main() {
val name: String? = "Kotlin"
name?.let {
println("Name is: $it") // Only runs if name != null
println("Length: ${it.length}")
}
val empty: String? = null
empty?.let {
println("This won't print")
}
}
Null Checks with ifโ
fun main() {
val input: String? = "42"
if (input != null) {
println(input.length) // Smart cast: input is String here
}
// Or use when
when {
input == null -> println("null value")
input.isEmpty() -> println("empty")
else -> println("value: $input")
}
}
requireNotNull and checkNotNullโ
fun process(name: String?) {
val safeName = requireNotNull(name) { "Name cannot be null" }
println("Processing: $safeName")
}
fun main() {
process("Alice")
// process(null) // Throws IllegalArgumentException
}
Nullable Collectionsโ
fun main() {
val list: List<String?> = listOf("A", null, "B", null, "C")
// Filter out nulls
val nonNullList = list.filterNotNull()
println(nonNullList) // [A, B, C]
// Map safely
val lengths = list.mapNotNull { it?.length }
println(lengths) // [1, 1, 1]
}
Summary Tableโ
| Operator / Function | Behavior |
|---|---|
Type? | Declare nullable type |
?. | Safe call โ returns null if receiver is null |
?: | Elvis โ default value if null |
!! | Force unwrap โ NPE if null |
as? | Safe cast โ null if cast fails |
?.let { } | Execute block only if non-null |
filterNotNull() | Remove nulls from collection |
requireNotNull() | Assert non-null with custom error |
Telemetry Integration
Completed working through this block? Sync progress to workspace.