Smart cast i sprawdzanie null
Gdy raz sprawdzisz, że coś nie jest null (albo jest konkretnego typu),
Kotlin to zapamiętuje i pozwala dalej używać wartości bez dodatkowych
znaków zapytania. To smart cast. Poznasz też sprawdzanie typu
(is / !is) oraz jawne rzutowanie (as / as?).
Czym jest smart cast
Smart cast (inteligentne rzutowanie) to wygoda, dzięki której Kotlin
sam „domyśla się” węższego typu po sprawdzeniu. Gdy upewnisz się, że wartość
String? nie jest null, Kotlin od tego miejsca traktuje ją
jak zwykły String – bez potrzeby dodatkowych zabiegów.
Innymi słowy: raz sprawdzone, dalej wiadome. Nie musisz powtarzać tej samej weryfikacji w kółko.
Smart cast po sprawdzeniu null
Znasz już sprawdzenie if (x != null) z poprzedniej lekcji. Tutaj nazwiemy
rzecz po imieniu: wewnątrz tego bloku działa smart cast – wartość
zmienia typ z String? na String.
fun opisz(imie: String?) { if (imie != null) { // tutaj imie ma typ String (smart cast z String?) println("Imię: $imie") println("Długość: ${imie.length}") // .length bez żadnego ? println("Wielkie: ${imie.uppercase()}") } else { println("Brak imienia") } }
Najpewniej zadziała dla val oraz lokalnych var, których
między sprawdzeniem a użyciem nie nadpisujesz. Skoro Kotlin musi mieć
pewność, że wartość nie stała się null „po drodze”,
unikaj zmieniania jej w międzyczasie.
Inne formy sprawdzenia
Smart cast działa nie tylko w klasycznym if/else. Dwa częste, wygodne
warianty:
fun dlugieImie(imie: String?) { // po lewej stronie && sprawdzamy null, po prawej już używamy if (imie != null && imie.length > 5) { println("Długie imię: $imie") } }
fun przywitaj(imie: String?) { if (imie == null) return // jeśli null – kończymy funkcję // od tego miejsca imie to String (gdyby było null, już by nas tu nie było) println("Cześć, $imie! Masz ${imie.length} liter w imieniu.") }
is / !is – sprawdzanie typu
Smart cast działa też przy sprawdzaniu typu wartości. Operator
is pyta „czy ta wartość jest danego typu?”, a !is – odwrotnie.
Po pozytywnym sprawdzeniu Kotlin traktuje wartość jak ten typ.
Any to najbardziej ogólny typ w Kotlinie – „cokolwiek”. Zmienna typu
Any może przechowywać tekst, liczbę albo dowolną inną wartość.
Pełny system typów poznasz w bloku o programowaniu obiektowym; tu Any
posłuży nam tylko do pokazania is i as.
val cos: Any = "Tekst" if (cos is String) { // smart cast: cos jest tu typu String println("To tekst o długości ${cos.length}") }
fun opisTekstu(x: Any) { if (x !is String) { println("To nie jest tekst") return } // tu x to już String println("Tekst wielkimi literami: ${x.uppercase()}") }
as – jawne rzutowanie
Czasem chcesz sam powiedzieć Kotlinowi: „potraktuj tę wartość jak
dany typ”. Służy do tego operator as. To jednak ryzykowne –
jeśli wartość nie jest tego typu, program przerwie się błędem
ClassCastException w czasie działania.
val cos: Any = "Tekst" val tekst = cos as String // mówimy: to jest String println(tekst.length) // 5 val liczba: Any = 42 // val zle = liczba as String // BŁĄD! 42 to nie String -> ClassCastException
Sięgaj po as wyłącznie, jeśli naprawdę wiesz, że
wartość jest danego typu (np. po wcześniejszym is). W razie wątpliwości
bezpieczniejsza jest wersja as? z następnej sekcji.
as? – bezpieczne rzutowanie
Operator as? próbuje rzutować, a gdy się nie uda, zamiast
błędu zwraca null. Wynik jest więc typu nullowalnego – i obsługujesz go
tak, jak każdą wartość, która może być pusta.
val cos: Any = 42 val tekst = cos as? String // 42 to nie String -> tekst = null println(tekst) // null (zamiast błędu) // wynik jest typu String?, więc obsługujemy null jak zwykle: if (tekst != null) { println(tekst.length) } else { println("To nie był tekst") }
| Operator | Gdy typ pasuje | Gdy typ NIE pasuje |
|---|---|---|
as | rzutuje | ❌ błąd ClassCastException |
as? | rzutuje | ✅ zwraca null |
Częste błędy
❌ Błąd 1: użycie poza blokiem sprawdzenia
❌ Smart cast nie sięga poza if
val imie: String? = pobierz()
if (imie != null) {
println(imie.length)
}
println(imie.length)
// Błąd! tutaj imie znów
// jest String?
✅ Użyj wewnątrz bloku
val imie: String? = pobierz()
if (imie != null) {
println(imie.length)
// cała praca tutaj
}
❌ Błąd 2: as zamiast as? przy niepewnym typie
❌ Może wybuchnąć
val cos: Any = 42 val t = cos as String // ClassCastException // w czasie działania!
✅ Bezpieczne as?
val cos: Any = 42 val t = cos as? String // t = null, brak błędu
❌ Błąd 3: użycie w odwrotnej gałęzi
❌ W tej gałęzi wartość JEST null
if (imie == null) {
println(imie.length)
// Błąd! tu imie jest null
✅ Sprawdzaj != null
if (imie != null) {
println(imie.length)
}
❌ Błąd 4: zbędne as po is
❌ Ręczne rzutowanie bez potrzeby
if (cos is String) {
val t = cos as String
println(t.length)
}
// as zbędne – smart cast
// już to zrobił
✅ Używaj wprost po is
if (cos is String) {
println(cos.length)
}
Podsumowanie
- Smart cast: po sprawdzeniu Kotlin sam zawęża typ – po
if (x != null)wartość staje się nienullowalna. - Smart cast działa też w warunku z
&&oraz po wczesnym powrocie (if (x == null) return). - Najpewniej zadziała na
vali niezmienianych lokalnychvar. is/!issprawdzają typ; po pozytywnymisrównież działa smart cast.asrzutuje wprost, ale przy niezgodnym typie powoduje ClassCastException.as?to bezpieczne rzutowanie – przy niepowodzeniu zwracanull(wynik typu nullowalnego).
Zadania do wykonania
Zadania z wczytywaniem uruchamiaj w IntelliJ IDEA; pozostałe w Kotlin Playground.
Napisz funkcję opisz(imie: String?), która po sprawdzeniu
if (imie != null) wypisze imię oraz jego długość (skorzystaj ze smart
castu – żadnych znaków zapytania przy .length).
Przepisz funkcję z zadania 1 tak, by używała wczesnego powrotu:
if (imie == null) return, a dalszy kod zakładał, że imię istnieje.
Napisz funkcję rozpoznaj(x: Any), która za pomocą is
sprawdzi, czy x to String czy Int, i wypisze
odpowiedni komunikat (np. długość tekstu albo podwojoną liczbę).
Zadeklaruj zmienną typu Any z dowolną wartością. Za pomocą
as? spróbuj potraktować ją jako String i obsłuż wynik:
jeśli rzutowanie się powiodło – wypisz tekst wielkimi literami, w przeciwnym razie
komunikat „To nie był tekst”. Przetestuj raz dla tekstu, raz dla liczby.