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?).

smart cast is / !is as as?
1

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.

2

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.

Po sprawdzeniu znika znak zapytania
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")
    }
}
Smart cast działa na zmiennych, które się nie zmieniają

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.

3

Inne formy sprawdzenia

Smart cast działa nie tylko w klasycznym if/else. Dwa częste, wygodne warianty:

Sprawdzenie w jednym warunku (&&)
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")
    }
}
Wczesny powrót (early return)
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.")
}
4

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.

Najpierw słówko o typie Any

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.

Rozpoznanie typu wartości
val cos: Any = "Tekst"

if (cos is String) {
    // smart cast: cos jest tu typu String
    println("To tekst o długości ${cos.length}")
}
!is z wczesnym powrotem
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()}")
}
5

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.

Rzutowanie wprost
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
as używaj tylko, gdy masz pewność

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.

6

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.

Rzutowanie, które nie wybucha
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")
}
OperatorGdy typ pasujeGdy typ NIE pasuje
asrzutuje❌ błąd ClassCastException
as?rzutuje✅ zwraca null
7

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)
}
8

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 val i niezmienianych lokalnych var.
  • is / !is sprawdzają typ; po pozytywnym is również działa smart cast.
  • as rzutuje wprost, ale przy niezgodnym typie powoduje ClassCastException.
  • as? to bezpieczne rzutowanie – przy niepowodzeniu zwraca null (wynik typu nullowalnego).
9

Zadania do wykonania

Zadania z wczytywaniem uruchamiaj w IntelliJ IDEA; pozostałe w Kotlin Playground.

Zadanie 1 łatwe

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).

Zadanie 2 łatwe

Przepisz funkcję z zadania 1 tak, by używała wczesnego powrotu: if (imie == null) return, a dalszy kod zakładał, że imię istnieje.

Zadanie 3 łatwe

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ę).

Zadanie 4 średnie

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.