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.