Moje odkazy
vydáno: 1. 7. 2017 22:16, aktualizováno: 1. 7. 2017 22:24
Ternární operátor většinou bereme jako stručnější, ale obsahově totožný, zápis if
-else
bloku, v jehož větvích jen nastavujeme proměnnou nebo vracíme hodnotu. Jsou ale vždy tyto zápisy ekvivalentní? Můžeme automaticky nahrazovat stávající kód ternárními operátory?
Jednoduchý příklad:
int a = 5;
String b;
if (a == 5) {
b = "OK";
} else {
b = "chyba";
}
System.out.println("Výsledek: " + b);
Tento kód můžeme bez obav přepsat s použitím ternárního operátoru a zkrátit:
int a = 5;
String b;
b = a == 5 ? "OK" : "chyba";
System.out.println("Výsledek: " + b);
A teď něco trochu složitějšího – mějme metodu pro převod textové reprezentace booleovské hodnoty, která podporuje tři stavy (true
, false
a null
– ano, ne a neznámá hodnota) a umožňuje nastavit i výchozí hodnotu, pokud původní hodnota byla neznámá:
public static Boolean toBoolean(String value, Boolean defaultValue) {
if (value == null) {
return defaultValue;
} else {
return "Y".equals(value);
}
}
Nechme teď stranou, že bychom spíš měli vyhazovat výjimku, pokud na vstupu bude něco jiného než Y
, N
nebo null
– dejme tomu, že zadání bylo:
null
je null
a cokoli různé od Y
se považuje za false
.
Blok if
-else
tohoto typu působí zbytečně a zřejmě budeme mít nutkání ho přepsat na ternární operátor:
public static Boolean toBoolean(String value, Boolean defaultValue) {
return value == null ? defaultValue : "Y".equals(value);
}
Ušetřili jsme pár řádků kódu a možná je to i přehlednější. Ale jsou tyto zápisy opravdu ekvivalentní? Bude to fungovat?
Na první pohled to vypadá dobře a kód je tak jednoduchý, že v něm snad ani nejde udělat chybu. Přesto nefunguje – za určitých okolností tato metoda vyhodí NullPointerException
– stane se tak ve chvíli, kdy jsou oba parametry null
, což ale není nijak nesmyslná situace – znamená to, že na vstupu byla neznámá hodnota (tzn. ani Y
ani N
) a nechceme nastavit žádnou výchozí hodnotu, chceme zachovat neznámou hodnotu, pokud byla na vstupu.
Ale jak to? Vždyť všude – na vstupu i výstupu – používáme Boolean
s velkým B, tedy objektový typ, který může být null
, a původní implementace s if
-else
nám fungovala za všech okolností.
Příčina chyby je v tom, že výraz s ternárním operátorem má jako celek nějaký datový typ – nejsou to dvě samostatné větve kódu jako u if
-else
bloku – a tento typ se odvozuje podle druhé a třetí části výrazu, v našem případě tedy:
defaultValue : // 2. část: Boolean
"Y".equals(value) // 3. část: boolean
A metoda equals()
vrací boolean
s malým b, tedy neobjektový typ, který nemůže být null
. Problém pak není s touto metodou (ta null
nevrátí ani nevyhodí výjimku), ale s tím, že by výraz s ternárním operátorem měl nabýt hodnoty null
(protože defaultValue = null
), ačkoli jeho typ je boolean
(nikoli Boolean
), který null
být nemůže. A není ani nic platné, že by tento boolean
byl vzápětí převeden na Boolean
, který tato metoda vrací.
Odvození typu proběhne podle specifikace:
If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.
If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.
Řešením je buď zachovat původní if
-else
nebo doplnit explicitní přetypování:
return value == null ? defaultValue : (Boolean) "Y".equals(value);
Ponaučení: oproti jiným jazykům obsahuje Java poměrně málo podobných záludností, ale přesto se vyplatí si i triviální kód vždy vyzkoušet a v rámci možností pro něj napsat i jednotkové testy.
Témata: [Java]