Lekce 6 - Typový systém: Null safety v Kotlin
V předešlém cvičení, Řešené úlohy k 5. lekci Kotlin, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
Nyní si konečně vysvětlíme koncept tzv. null safety, který je v Kotlin velmi důležitý a řekneme si, co znamenají všechny ty vykřičníky ve zdrojovém kódu. S několika jsme se již setkali a byť se může pro začátek jednat o složitější koncept, tak bude lepší, když budeme alespoň tušit, o co se jedná.
Koncept hodnoty null
Programovací jazyky se musí nějak vypořádat se situací, kdy proměnná
nemá žádnou hodnotu. S takovým problémem se často setkáváme u funkcí,
které se nemusí provést korektně. Pokud se např. nepodaří načtení
čísla z konzole, nemělo by být vráceno žádné číslo, ale "prázdno".
Pokud by nám Kotlin vrátil v tomto případě např. hodnotu 0
nebo -1
, nepoznali bychom zda se číslo nepodařilo načíst nebo
zda uživatel vložil právě 0
nebo -1
. Za tímto
účelem se vymyslela speciální hodnota null
, která bezpečně
označí, že je proměnná prázdná.
Pokud si v Kotlin vytvoříme standardní proměnnou, prázdnou hodnotu
null
do ní přiřadit nelze:
// Tento kód je chybný var cislo = 15 cislo = null // Tento řádek vyvolá chybu
Někdo by ji tam totiž nemusel očekávat. Abychom null
mohli
do proměnné přiřadit, musíme proměnnou nejdříve deklarovat jako
nullovatelnou.
Nullovatelné typy
Nullovatelný typ můžeme chápat jako jakýsi box, který slouží k
zabalení obyčejné proměnné. Box vždy existuje, po jeho otevření
ale hodnotu buď najdeme nebo je prázdný. Nullable typ vytvoříme
tak, že za název datového typu proměnné umístíme
otazník ?
. Zkusme si to:
var moznaCislo: Int? = 15 moznaCislo = null
Kód se již přeložil v pořádku a proměnná moznaCislo
je
nyní prázdná, i když se jedná o číslo. To zní zatím dobře, že?
Je tu ovšem problém, který mnoho ostatních programovacích jazyků
nedokázalo vyřešit. S moznaCislo
by nám nyní nemělo
být umožněno pracovat jako s obyčejnou proměnnou. Pokud bychom
napsali:
var moznaCislo: Int? = 15 moznaCislo = null println(moznaCislo * 2)
a program se přeložil, mohl by za běhu spadnout v případě, že by
moznaCislo
bylo prázdné. Nemůžeme přeci vynásobit "nezadáno"
dvěma. Když si takový program zkusíte napsat, zjistíte, že nejde
přeložit. Podobně by nám Kotlin vynadal i při přístupu k
vlastnosti nebo metodě nullovatelného typu. Můžete si zkusit, že kvůli
výpisu délky druhého řetězce nepůjde následující kód přeložit:
var s1 = "Ahoj" var s2: String? = "Světe" println(s1.length) println(s2.length)
Asi jste tušili, že Kotlin nepatří mezi jazyky, které by si tento problém neohlídaly
Null safety
Mechanismus, který již při překladu kontroluje jak nullovatelné typy používáme, se nazývá null safety. Existuje několik způsobů jak nullovatelnou proměnnou použít. Postupně si je vyzkoušíme.
Operátor !!
Začněme tím nejhloupějším, který jsme zatím v kurzu používali, aby
toho na nás nebylo ze začátku moc. Pomocí operátoru !!
můžeme Kotlin degradovat na starší jazyky jako je např. Java a kontrolu null safety vypnout. Pokud v
proměnné zrovna nebude null
, vše bude fungovat:
{KOTLIN_CONSOLE}
var moznaCislo: Int? = 15
println(moznaCislo!! * 2)
{/KOTLIN_CONSOLE}
Výsledek:
30
Pokud v ní ovšem prázdná hodnota bude, celá aplikace za běhu upadne s chybou:
var moznaCislo: Int? = 15 moznaCislo = null println(moznaCislo!! * 2)
Jelikož při překladu bychom na tuto chybu vůbec nepřišli, nebudeme
řešení s !!
příliš používat.
Podmínka
O něco chytřejší řešení je pracovat s nullovatelnými typy v podmínce
na hodnotu null
. Jelikož se tak vyhneme pádu programu, Kotlin
nám program dovolí přeložit:
{KOTLIN_CONSOLE}
var moznaCislo: Int? = 15
if (moznaCislo != null)
println(moznaCislo * 2)
else
println("Číslo není zadané!")
{/KOTLIN_CONSOLE}
Bezpečné volání
Určitě tušíte, že existuje lepší řešení než psát stále podmínky
na null
. Pomocí operátoru ?.
(otazník tečka) se
buď přistoupí k dané vlastnosti nebo se vrátí null
v
případě, že je proměnná prázdná.
?.let
Pokud bychom použili bezpečné volání spolu s klíčovým slovem
let
, spustil by se kód ve složených závorkách pouze v
případě, že by v proměnné byla ne-nullová hodnota:
{KOTLIN_CONSOLE}
var moznaCislo: Int? = 15
moznaCislo?.let { println(it) }
{/KOTLIN_CONSOLE}
Klíčové slovo it
následně v bloku obsahuje tuto hodnotu.
Pokud by bylo moznaCislo
null
, program by se
přeložil a výpis by se nespustil.
Řetězení ?.
Tuto funkcionalitu využijeme pouze v případě, když se chceme zeptat přes řetěz vlastností jako např.:
zak?.ucitel?.nadrizeny?.jmeno
Výraz výše vrátí buď název ředitele školy (nadřízeného učitele
žáka) nebo null
v případě, že je jakýkoli článek výrazu
prázdný. Ušetříme tak spoustu podmínek, ovšem musíme pamatovat na to,
že ve výsledku máme stále ač jeden tak stále nullovatelný typ.
Mohli bychom takto i bezpečně přiřadit, již bez potřeby dalších podmínek:
zak?.ucitel?.nadrizeny?.jmeno = "Seymour Skinner"
Elvis operátor
Jak vznikl název tohoto operátoru asi netřeba vysvětlovat Elvise používáme spolu s
operátorem ?.
a umožňuje nám zeptat se, zda je hodnota v
nullovatelné proměnné null
a případně použít jinou
výchozí hodnotu. Opět si to zkusme na našem příkladu:
{KOTLIN_CONSOLE}
var moznaText: String? = "Ahoj světe"
println(moznaText?.length ?: 0)
{/KOTLIN_CONSOLE}
Na pravé straně Elvis operátoru můžeme použít i return
nebo vyvolání výjimek (viz další kurzy).
V každém ze způsobů práce s hodnotou null
platí, že pokud je hodnota null
, další výrazy (metody)
určené pro případ kdyby null
nebyla se nespustí.
V příští lekci, Pole v Kotlin, se budeme věnovat polím.