Wyrażenie warunkowe (conditional operator)

Spis treści

W dzisiejszym wydaniu serii JavaTraps zajmiemy się conditional operatorem (inaczej ternary operator, operator trójargumentowy, lub wyrażenie warunkowe). Przypomnijmy jego składnię:

(condition or expression)? operand1 : operand2;
 
 // co jest równoważne z:
 if (condition)
     operand1;
 else
     operand2;

Tyle wstępem przypomnienia, a teraz przejdźmy do dzisiejszej zagadki. Pochodzi ona z serii Java Puzzlers, która zainspirowała mnie wczoraj. Pytanie brzmi oczywiście, co wyświetli się na ekranie?

import java.util.Random;
 
 public class JavaTraps002 {
 	public static void main(String args[]) {
 		Random rand = new Random();
 		boolean check = rand.nextBoolean();
 
 		Number number = (check || !check)?
 				new Integer(1) : new Double(2.0);
 
 		System.out.println("Wynik: "+number);
 	}
 }

Kurs Java

Jak widać przykład nie jest strasznie skomplikowany, dostępne odpowiedzi to:

A) Wynik: 1

B) Wynik: 2.0

C) Błąd kompilacji

D) Inna odpowiedź

Rozwiązanie

Przypomnijmy sobie jak wyglądał kod:

import java.util.Random;
 
 public class JavaTraps002 {
 	public static void main(String args[]) {
 		Random rand = new Random();
 		boolean check = rand.nextBoolean();
 
 		Number number = (check || !check)?
 				new Integer(1) : new Double(2.0);
 
 		System.out.println("Wynik: "+number);
 	}
 }

A poprawna odpowiedź to:

D) Inna odpowiedź. Wyświetlone zostanie Wynik: 1.0

Zastanawiacie się jak to możliwe? Już wyjaśniam.

W pierwszej kolejności tworzymy sobie obiekt random do wygenerowania jednej wartości typu boolean. Tutaj oczywiście nie ma żadnej pułapki. Następnie chcemy utworzyć obiekt typu Number i przypisać do niego, albo wartość Integer(1), lub Double(2.0). Ponieważ wyrażenie (check || !check) zawsze zwraca true . Spodziewamy się, więc, że do referencji number zostanie przypisany obiekt new Integer(1) . No i w zasadzie tak się dzieje. Jest tylko jedno małe ale... Wyświetlając wartość number widzimy na ekranie 1.0. Jakim cudem?

W specyfikacji języka odnajdujemy odpowiedź dlaczego tak się dzieje. Konkretnie chodzi o zapis:

Otherwise, binary numeric promotion (§5.6.2) is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands. Note that binary numeric promotion performs unboxing conversion (§5.1.8) and value set conversion (§5.1.13).

W naszym przypadku, kompilator przewiduje, że wynikiem zawsze będzie typ numeryczny: Integer, albo Double. Następuje więc unboxing do typów prostych int i double, a następnie promocja do najogólniejszego typu, w naszym przypadku int -> double. W następnej kolejności następuje boxing i w wyniku mamy dwie wartości typu Double.

Podobnego zachowania możemy się spodziewać w podobnych przypadkach np Integer, oraz Float, lub innych wymienionych w specyfikacji.

Jak się bronić przed tym zachowaniem?

Najlepiej nie używać conditional operatora w miejscach, gdzie nie jesteśmy pewni jak się zachowa. Bezpieczniej będzie użyć w takim przypadku zwykłego rozgałęzionego warunku if.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.