Lekce 13 - Dynamic, var, null a null aware operátory
V předešlém cvičení, Řešené úlohy k 11.-12. lekci Dartu, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V minulé lekci kurzu, Řešené úlohy k 11.-12. lekci Dartu, jsme se naučili pracovat s matematikou. V
této lekci se zaměříme na datový typ dynamic a hodnotu
null. O té jsme se již v rámci kurzu nejednou zmiňovali a dnes
si ukážeme jak s ní pracovat.
Datový typ dynamic
Datový typ dynamic se hodí v situacích, kdy nevíte datový
typ proměnné, nebo ho nechcete určovat. V proměnné tak může být naprosto
cokoli a kdykoli se může měnit i její datový typ. V jedné chvíli tak v
proměnné alfa může být celé číslo a poté do ní
přiřadíme řetězec:
dynamic alfa = 5; print(alfa); alfa = 'aaa'; print(alfa);
S takovou proměnnou se nám ale velmi špatně pracuje, jelikož nevíme my
ani IDE, co v ní právě je uloženo. Tím pádem nám IDE neumí napovídat.
Pokud to není nezbytně nutné nebo vhodné, dynamic bychom
používat neměli a raději bychom jej měli nahradit za konkrétní datový
typ.
var
Zmíníme ještě jedno klíčové slovo, kterým je var. Psát
datové typy je občas zbytečné, např. v momentě, kdy víme, jaký datový
typ dosazujeme, protože jsme ho např. definovali už někde výše.
Představme si, že máme např. 2 proměnné, kde obě mají nějaký velmi
složitý datový typ:
List<List<List<List<List<List<int>>>>>> a = []; List<List<List<List<List<List<int>>>>>> b = a;
Psát takovéto typy 2x není zas až takový problém. Psát je už ale
stále dokola je zbytečné a hlavně nepřehledné. Proto v momentě, kdy je
kód dostatečně přehledný i pochopitelný bez opakování datového typu,
můžeme využít klíčové slovo var, které nám, jednoduše
řečeno, dosadí ten správný typ a zpřehlední tím kód.
List<List<List<List<List<List<int>>>>>> a = []; var b = a;
Stejně tak můžeme klíčové slovo var použít kdekoli, kde dosazujeme literál.
var a = 1; // int var b = 1.0; // double var c = 'ahoj'; // String
Pozor však na situace, kdy bychom chtěli mít např. seznam prvků s
libovolným datovým typem. Literál by ovšem obsahoval jen jeden datový typ,
což var vyhodnotí jako seznam jen tohoto jednoho datového
typu:
var a = [1, 2, 3, 4, 5]; // List<int> // a.add('alfa'); ... chyba var b = [1, 2, 3, 4, 5, 'alfa']; // List<dynamic> b.add('beta'); var c = <dynamic>[1, 2, 3, 4, 5]; // List<dynamic> c.add('alfa');
V prvním případě, pokud bychom se pokusili přidat řetězec do seznamu, nám vyhubuje i statická analýza kódu, která vypíše chybu "error: The argument type 'String' can't be assigned to the parameter type 'int'. (argument_type_not_assignable at [muj_projekt] bin\muj_projekt.dart:8)".
Všimněte si, že datový typ prvků seznamu můžeme vynutit napsáním
typu do špičatých závorek < > před literál.
Užívejte tedy var pouze v případech, pokud kód neztratí na
přehlednosti a pokud jste si jisti, že se vyhodnotí správně. Var by se nám
neměl plést s dynamic, var za nás pouze typ sám
napíše, ale neumožňuje jej měnit a proměnná se chová stejně, jako
bychom typ napsali ručně.
Hodnota null
Několikrát jsme si zmínili hodnotu null a sliboval jsem, že
si ji dovysvětlíme. Cokoli v Dartu, co vytvoříme, ale nic do toho
nepřiřadíme, má výchozí hodnotu null. Hodnotu
null můžeme chápat jako "nic"; dokonce až takové "nic", že na
ní nemůžeme volat téměř žádné metody nebo vlastnosti a pokud to
uděláme, program se většinou ukončí s chybou.
Pozn.: Na null můžeme ve skutečnosti volat pouze metody,
které má Object. Nám prozatím postačí vědět, že je to
například metoda toString(). Tento fakt ale nic nemění na
okolnostech, že náš program může spadnout, což rozhodně
nechceme.
Že se nám program opravdu ukončí s chybou si ukážeme na příkladu výpisu absolutní hodnoty čísla:
int cislo; // obsahuje null print(cislo.abs());
Výstup programu:
Konzolová aplikace
Unhandled exception:
NoSuchMethodError: The method 'abs' was called on null.
Receiver: null
Tried calling: abs()
#0 Object._noSuchMethod (dart:core-patch/object_patch.dart:43)
#1 Object.noSuchMethod (dart:core-patch/object_patch.dart:47)
#2 main (file:///D:/git/muj_projekt/bin/muj_projekt.dart:5:15)
#3 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:265)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:151)
Již z minula víme, že hodnota null se dá využít i na
dobré věci (např. detekci, že jsme nenačetli opravdu nic, místo toho,
abychom hodnotu nastavili např. na 0), ale nyní jsme také
zjistili, že je null potenciálně nebezpečný a musíme s ním
zacházet opatrně.
Nejjednodušší ochranou, kterou byste již měli být schopni sami zvládnout, jsou podmínky:
int cislo; // obsahuje null if (cislo != null) { print(cislo.abs()); } else { print('Číslo neexistuje.'); }
Výstup programu:
Konzolová aplikace
Číslo neexistuje.
Asi ale sami tušíte, že psát podmínku vždy, když si nejsme jistí, že
proměnná je nebo není null není moc hezké. A není to ani
zrovna dvakrát přehledné přehledné. Naštěstí jsou v Dartu tzv.
null-aware operátory, které nám s tím hodně pomohou.
Null-aware operátory
Operátor ??
Operátor ?? použijeme všude tam, kde chceme vrátit hodnotu
výrazu a v případně, že byl výraz null, pak vrátit jeho
alternativu.
int cislo; // obsahuje null print(cislo ?? 'Nic.'); cislo = 5; print(cislo ?? 'Nic.');
Výstup programu:
Konzolová aplikace
Nic.
5
Operátor ??=
Operátor ??= použijeme všude tam, kde chceme přiřadit
alternativní hodnotu, pokud je původní hodnota proměnné null.
Výsledek po použití operátoru je samozřejmě buď původní hodnota, pokud
nebyla null, nebo alternativa.
int cislo; // obsahuje null print(cislo ??= 42); print(cislo); int druheCislo; // obsahuje null druheCislo ??= 666; print(druheCislo);
Výstup programu:
Konzolová aplikace
42
42
666
Operátor ?.
Operátor ?. použijeme všude tam, kde chceme volat metodu a
nejsme si jisti, jestli není výraz null. Pokud je
null, nic se neprovede a výraz vrací hodnota
null.
Ukážeme si náš původní příklad s absolutní hodnotou:
int cislo; // obsahuje null print(cislo?.abs());
Výstup programu:
Konzolová aplikace
null
Pokud bychom chtěli volat více metod za sebou, třeba zjistit, jestli je
absolutní hodnota sudá, použijeme operátor ?. v celém výrazu
vícekrát.
int cislo; // obsahuje null print(cislo?.abs()?.isEven); cislo = -8; print(cislo?.abs()?.isEven); cislo = 7; print(cislo?.abs()?.isEven);
Výstup programu:
Konzolová aplikace
null
true
false
Jednotlivé operátory můžeme samozřejmě kombinovat:
int cislo; // obsahuje null print(cislo?.abs() ?? 'Číslo neexistuje.');
Výstup programu:
Konzolová aplikace
Číslo neexistuje.
Už jsme skoro u konce. Příští lekce, Ekosystém a konvence Dartu, je vlastně bonusová a nebude se týkat kódu, ale ekosystému Dart programů a konvencím v Dartu.
