FK~

Moje odkazy

Ostatní odkazy

Close Windows
Nenajdete mě na Facebooku ani Twitteru
Rozpad EU
Jsem členem FSF
There Is No Cloud …just other people's computers.
Sane software manifesto / Manifest příčetného softwaru

Java a záludnost ternárního operátoru

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?

Logo OpenJDK (průhledné 1)

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, falsenull – 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.

Odkazy a zdroje:

Témata: [Java]

Komentáře čtenářů


Michal, 19. 2. 2019 15:52 [odpovědět]

Díky, za objasnění pěkného Java "špeku" :-)

Přidat komentář

reagujete na jiný komentář (zrušit)
jméno nebo přezdívka
název příspěvku
webová stránka, blog
e-mailová adresa
nápověda: možnosti formátování
ochrana proti spamu a špatným trollům

Náhled komentáře