Typy null
Jedna z najważniejszych cech Kotlina: język rozdziela typy, które nigdy
nie są puste (String), od tych, które mogą być puste
(String?). Dowiesz się, czym jest null i błąd
NullPointerException oraz jak Kotlin chroni Cię przed nim już na etapie pisania kodu.
Czym jest null
null oznacza brak wartości – „nic”, „pustka”.
To coś innego niż zero czy pusty tekst: zmienna o wartości null nie
wskazuje na żadne dane. Pojawia się np. wtedy, gdy czegoś nie udało się znaleźć
albo nie podano (pamiętasz toIntOrNull() z lekcji o wczytywaniu?).
| Wartość | Znaczenie |
|---|---|
0 | liczba zero – konkretna wartość |
"" | pusty tekst – istnieje, tylko nic nie zawiera |
null | brak jakiejkolwiek wartości |
NullPointerException – groźny błąd
Problem zaczyna się, gdy program próbuje coś zrobić z wartością,
której nie ma. Próba odwołania się do null (np. odczytania długości
nieistniejącego tekstu) powoduje błąd NullPointerException, w skrócie
NPE – jeden z najczęstszych powodów „wysypywania się” programów.
Pomysł wartości null ma już dziesięciolecia, a jego twórca sam nazwał
go później swoim „błędem wartym miliard dolarów” – tyle kosztowały świat awarie
spowodowane odwołaniami do nieistniejących wartości. Kotlin powstał m.in. po to,
by ten problem rozwiązać.
Kluczowa idea Kotlina: zamiast pozwalać, by NPE wybuchł w czasie działania programu (u użytkownika!), język wyłapuje ryzyko już podczas pisania kodu.
Typy nienullowalne
Domyślnie typy w Kotlinie są nienullowalne – nie wolno przypisać im
null. Zmienna typu String zawsze zawiera
jakiś tekst, więc można jej bezpiecznie używać.
val imie: String = "Anna" // imie = null // BŁĄD już przy pisaniu – String nie może być null println(imie.length) // bezpieczne – imie na pewno istnieje println(imie.uppercase()) // bezpieczne
Gdy widzisz typ bez znaku zapytania (String, Int,
Double…), masz pewność, że wartość istnieje. Nie musisz
niczego sprawdzać – po prostu jej używasz.
Typy nullowalne – znak zapytania
Czasem brak wartości jest naturalny (np. nieznaleziony wynik). Aby pozwolić
zmiennej być pustą, dopisujemy do typu znak zapytania: String?. Taki typ
może zawierać tekst albo null.
val imie: String? = "Anna" // może mieć tekst... val drugie: String? = null // ...albo być puste
| Typ | Dozwolone wartości |
|---|---|
String | tylko tekst (nigdy null) |
String? | tekst albo null |
Int | tylko liczba |
Int? | liczba albo null |
Znak zapytania to świadoma decyzja: „ta wartość naprawdę może nie istnieć”.
Jeśli wartość zawsze istnieje, nie dodawaj ? – dzięki
temu unikasz późniejszych komplikacji.
Jak Kotlin zapobiega NPE
Skoro String? może być null, Kotlin nie pozwoli
użyć go wprost – bo groziłoby to NPE. Próba odczytania np. .length na
wartości nullowalnej to błąd już podczas pisania:
val imie: String? = pobierzImie() // println(imie.length) // BŁĄD! imie może być null
Jednym z najprostszych sposobów jest sprawdzenie, czy wartość nie jest
null. Wewnątrz takiego warunku Kotlin wie, że wartość istnieje, i pozwala
jej użyć:
val imie: String? = pobierzImie() if (imie != null) { println(imie.length) // OK – w tym miejscu imie na pewno nie jest null } else { println("Brak imienia") }
To tylko jeden ze sposobów. W następnej lekcji poznasz wygodne operatory
?., ?: i !!, a mechanizm „Kotlin wie, że tu
nie ma null” (tzw. smart cast) rozłożymy na części osobno. Tu najważniejsze:
nullowalnego nie da się użyć wprost – i to właśnie chroni przed NPE.
Przypisania między typami
Kierunek ma znaczenie. Wartość nienullowalną zawsze można włożyć
tam, gdzie dozwolony jest null (bo „na pewno coś jest” spełnia warunek
„coś albo nic”). Odwrotnie już nie – nullowalnej nie wolno przypisać
do typu, który nie dopuszcza null.
val pewne: String = "Anna" val moze: String? = pewne // OK – nie-null pasuje do nullowalnego val cosNull: String? = null // val pewne2: String = cosNull // BŁĄD! nullowalne nie pasuje do nienullowalnego
„Pewne” mieści się w „może” – ale „może” nie mieści się w „pewne”. Aby przejść
z String? do String, trzeba najpierw rozprawić się
z możliwością null (o tym w kolejnych lekcjach).
Częste błędy
❌ Błąd 1: null w typie nienullowalnym
❌ String nie przyjmie null
val imie: String = null // Błąd! String nie może // być null
✅ Dodaj ?, jeśli null ma być możliwy
val imie: String? = null // OK
❌ Błąd 2: bezpośrednie użycie nullowalnego
❌ Użycie bez sprawdzenia
val imie: String? = pobierz() println(imie.length) // Błąd! imie może być null
✅ Najpierw sprawdź
val imie: String? = pobierz()
if (imie != null) {
println(imie.length)
}
❌ Błąd 3: nullowalne tam, gdzie wymagane nie-null
❌ Przypisanie w złą stronę
val moze: String? = "tekst" val pewne: String = moze // Błąd! moze mogłoby // być null
✅ Typy zgodne
val moze: String? = "tekst" val nadalMoze: String? = moze // OK
❌ Błąd 4: zapomniany ? przy wyniku, który bywa null
❌ toIntOrNull zwraca Int?
val liczba: Int =
readln().toIntOrNull()
// Błąd! wynik może
// być null
✅ Typ nullowalny Int?
val liczba: Int? =
readln().toIntOrNull()
// OK
Podsumowanie
nulloznacza brak wartości – to nie to samo co 0 czy pusty tekst.- Odwołanie do
nullpowoduje błąd NullPointerException (NPE) w czasie działania programu. - Domyślnie typy są nienullowalne (
String) – zawsze mają wartość, można ich bezpiecznie używać. - Dopisanie
?tworzy typ nullowalny (String?) – może zawierać wartość albonull. - Kotlin nie pozwala użyć nullowalnego wprost – wyłapuje ryzyko NPE już podczas pisania kodu.
- Nie-null można przypisać do nullowalnego, ale nie odwrotnie (bez wcześniejszego sprawdzenia).
Zadania do wykonania
Zadania z wczytywaniem uruchamiaj w IntelliJ IDEA; pozostałe w Kotlin Playground.
Zadeklaruj zmienną typu String z dowolnym tekstem oraz zmienną typu
String? o wartości null. Wypisz obie.
Spróbuj przypisać null do zmiennej typu String. Zaobserwuj
komunikat błędu, a następnie popraw typ na String?.
Zadeklaruj zmienną String? i spróbuj wypisać jej .length
bezpośrednio. Zobacz błąd, a potem owiń odwołanie w sprawdzenie
if (x != null) { ... }.
Wczytaj liczbę przez readln().toIntOrNull() (wynik typu Int?).
Za pomocą if sprawdź, czy nie jest null: jeśli tak – wypisz
„Nieprawidłowa liczba”, w przeciwnym razie wypisz jej kwadrat. Zwróć uwagę, że dopiero
po sprawdzeniu Kotlin pozwala użyć wartości w obliczeniach.