I tripped across a really strange NullPointerException the other day caused by an unexpected type-cast in the ternary operator. Given this (useless exemplary) function:
Integer getNumber() {
return null;
}
I was expecting the following two code segments to be exactly identical after compilation:
Integer number;
if (condition) {
number = getNumber();
} else {
number = 0;
}
vs.
Integer number = (condition) ? getNumber() : 0;
.
Turns out, if condition is true, the if-statement works fine, while the ternary opration in the second code segment throws a NullPointerException. It seems as though the ternary operation has decided to type-cast both choices to int before auto-boxing the result back into an Integer!?! In fact, if I explicitly cast the 0 to Integer, the exception goes away. In other words:
Integer number = (condition) ? getNumber() : 0;
is not the same as:
Integer number = (condition) ? getNumber() : (Integer) 0;
.
So, it seems that there is a byte-code difference between the ternary operator and an equivalent if-else-statement (something I didn't expect). Which raises three questions: Why is there a difference? Is this a bug in the ternary implementation or is there a reason for the type cast? Given there is a difference, is the ternary operation more or less performant than an equivalent if-statement (I know, the difference can't be huge, but still)?
According to JLS: -
The type of a conditional expression is determined as follows:
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.
The problem is that:
Integer number = (condition) ? getNumber() : 0;
Forces an unboxing and reboxing of the result of getNumber(). This is because the false part of the ternary (0) is an integer, so it tries to convert the result of getNumber() to an int. Whereas the following does not:
Integer number = (condition) ? getNumber() : (Integer) 0;
This is not a bug, just the way Java chose to do things.
This is how it is supposed to work. The ternary operator is not meant to be equivalent to a regular if statement. The bodies of if and else are statements, while the parts following ? and : are expressions, that are required to evaluate to the same type.
Put another way: a = b ? c : d is not supposed to be equivalent to if (b) a = c; else a = d;. Instead, b ? c : d is an expression on its own, and the assignment of its result to a won't affect the outcome.
Related
I have following code:
public String myMethod(String keyValue) {
Map<String, Integer> keyValueToRowIndex = ...
Integer rowIndex = (keyValue == null) ? 0 : keyValueToRowIndex.get(keyValue);
if (rowIndex == null)
return null;
...
}
Eclipse gives a "dead code" warning on the return null;. Removing the test for keyValue == null also removes the warning but I don't see how that extra test makes the return statement dead code. Clearly if the map contains no entry for some non-null keyValue, then rowIndex can still be null. Or am I missing something here?
I've seen similar Eclipse issues (here for instance), but this one seems a different and more trivial one.
The (surprising) short answer: Eclipse is right! This is dead code!
Reason
The important part is the ternary expression in the following line of code:
Integer rowIndex = (keyValue == null) ? 0 : keyValueToRowIndex.get(keyValue);
The Java language specification (JLS) says about the "Conditional Operator ?", that, if the first expression is of type int, and the second expression is of type Integer, the type of the entire expression will be int.
In your case, the first expression is the constant literal value 0, which is an int. The second expression is the result of the get method, which returns an object of type Integer. So according to the JLS, the entire expression has primitive type int!
This means, if the second expression (the get-call) will be evaluated, the result will be unboxed from Integer to int. This int value will then be auto-boxed again into an Integer to be able to assign it to the left operand, rowIndex.
But what happens, if the map returns a null value? In this case, unboxing from Integer to int is not possible, and a NullPointerExpression will be thrown!
So eclipse is right, as your expression can never return null, rowIndex will never be null either and the then-block of your if-statement will never be executed and hence is dead code!
Solution
The solution is simple: Use an Integer object instead of an primitive int value for your first expression:
Integer rowIndex = (keyValue == null) ? Integer.valueOf(0) : keyValueToRowIndex.get(keyValue);
My guess is that line 3 is interpreted as
Integer rowIndex = Integer.valueOf((keyValue == null) ? 0 : keyValueToRowIndex.get(keyValue).intValue());
(so both arguments to ?: are unified as int) - oddly enogh, Eclipse now shows no warning, even if it is now obvious that rowIndex is never null...
You might also replace the 0 with Integer.valueOf(0) to make the warning disappear.
Just spent a frustrating couple of hours debugging this code:
LinkedHashMap<String, Integer> rsrqs = new LinkedHashMap<String, Integer>();
Integer boxedPci = 52;
Integer boxedRsrq = boxedPci != null ? rsrqs.get(boxedPci.toString()) : -1;
The above produces a NullPointerException. The below code doesn't:
LinkedHashMap<String, Integer> rsrqs = new LinkedHashMap<String, Integer>();
Integer boxedPci = 52;
Integer boxedRsrq = boxedPci != null ? rsrqs.get(boxedPci.toString()) : Integer.valueOf(-1);
The only difference is wrapping the -1 with Integer.valueOf(). I'm sure I'm going to smack my forehead once somebody explains why this code behaves the way it does.. but can someone explain to me why this code behaves the way it does :)?
--
Edit
On second thought, I suspect that the NPE is coming from the rsrqs.get() returning null, which I think java is attempting to unbox into an int, before boxing back to an Integer. The Integer.valueOf() forces Java to do the unbox-box step. Moral of the story; don't just ignore those boxing warnings in Eclipse ;)
Ternary expressions, like any expression, have a type that is determined by the compiler. If the two sides of the ternary expression have what looks like different types, then the compiler will try and find a common base type using the least ambiguous of the two options. In your case, the -1 is least ambiguous, and so the type of the ternary expression is int. Sadly, the compiler doesn't use type inference based on the receiving variable.
The expression rsrqs.get(boxedPci.toString()) is then evaluated and forced into type int to match the ternary expression, but because it's null it throws the NPE.
By boxing the -1, the value of the ternary expression is Integer, and so you're null-safe.
The explanation can be concluded from the information in java language specification: 15.25. Conditional Operator ? :.
From the table there, you get the information, that, if the second operand (rsrqs.get(boxedPci.toString())) is of type Integer and the third operand is of type int, the result will be of type int.
That however means, that
Integer boxedRsrq = boxedPci != null ? rsrqs.get(boxedPci.toString()) : -1;
is semantically the same as
Integer boxedRsrq = boxedPci != null ? ((int)rsrqs.get(boxedPci.toString())) : -1;
But that means you get a NullPointerException, if you get null from the map, which obviously happens.
If you cast the third operand to Integer, the second operand will never be cast to int and no NPE happens.
1
is an int, not an Integer. So, Java is going to un-box your Integer to int, which causes the NullPointerException. When you auto-unbox a null Integer, it results in a NullPointerException. ( reference here )
But when you use
Integer.valueOf(-1)
it doesn't need to auto-unbox it, which leads to no exceptions.
Well, Integer.valueOf(String) returns an Integer and -1 is a primitive int. The first example is forced to unbox because one term is a primitive. You could also have used
Integer boxedRsrq = boxedPci != null ?
rsrqs.get(boxedPci.toString()) : (Integer) -1;
which would have boxed the -1.
I tripped across a really strange NullPointerException the other day caused by an unexpected type-cast in the ternary operator. Given this (useless exemplary) function:
Integer getNumber() {
return null;
}
I was expecting the following two code segments to be exactly identical after compilation:
Integer number;
if (condition) {
number = getNumber();
} else {
number = 0;
}
vs.
Integer number = (condition) ? getNumber() : 0;
.
Turns out, if condition is true, the if-statement works fine, while the ternary opration in the second code segment throws a NullPointerException. It seems as though the ternary operation has decided to type-cast both choices to int before auto-boxing the result back into an Integer!?! In fact, if I explicitly cast the 0 to Integer, the exception goes away. In other words:
Integer number = (condition) ? getNumber() : 0;
is not the same as:
Integer number = (condition) ? getNumber() : (Integer) 0;
.
So, it seems that there is a byte-code difference between the ternary operator and an equivalent if-else-statement (something I didn't expect). Which raises three questions: Why is there a difference? Is this a bug in the ternary implementation or is there a reason for the type cast? Given there is a difference, is the ternary operation more or less performant than an equivalent if-statement (I know, the difference can't be huge, but still)?
According to JLS: -
The type of a conditional expression is determined as follows:
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.
The problem is that:
Integer number = (condition) ? getNumber() : 0;
Forces an unboxing and reboxing of the result of getNumber(). This is because the false part of the ternary (0) is an integer, so it tries to convert the result of getNumber() to an int. Whereas the following does not:
Integer number = (condition) ? getNumber() : (Integer) 0;
This is not a bug, just the way Java chose to do things.
This is how it is supposed to work. The ternary operator is not meant to be equivalent to a regular if statement. The bodies of if and else are statements, while the parts following ? and : are expressions, that are required to evaluate to the same type.
Put another way: a = b ? c : d is not supposed to be equivalent to if (b) a = c; else a = d;. Instead, b ? c : d is an expression on its own, and the assignment of its result to a won't affect the outcome.
I have the following code,
class Foo<K> {
public <T> T createK() {
return null;
}
public void foo() throws ClassNotFoundException {
K k = (1==1)?null:createK();
}
}
However, it didn't compile. It caused the following compilation error (Oracle Java 7) on the line with the conditional operator:
Type mismatch: cannot convert from Object to K
When I rewrite the foo() method as follows,
public void foo() throws ClassNotFoundException {
K k = null;
if (1==1)
k = null;
else
k = createK();
}
Then it compiles fine. How is this caused and how can I solve it?
Although the conditional operator is not exactly same as if-else conditional, but this is a different issue.
This is a known bug with type inference of generic type parameter with conditional operator which doesn't work as expected.
The solution is to provide explicit type argument:
K k1 = (1==1) ? null : this.<K>createK();
...this would work, however with compiler warning - Dead Code (of course, due to 1 == 1 part). Note, how we need to explicit use this to invoke the method with explicit type argument. Simply doing <K>createK() won't work.
Adding the explicit type argument with method invocation - this.<K>createK(); forces the type T to be inferred as K, which is otherwise inferred as Object.
However, I suspect that your doubt is really related to the type inference. It's just a coincidence that the issue came out to be that. Even though, to get some idea about how conditional operators work in different situations, and what the type of its result is, you can schedule a visit to JLS §15.25 - Conditional Operator
No, the ternary (i.e. the ? :) is a very different beast from if ... then ... else.
The ternary is an operator and the two alternatives have to be expressions evaluating to the same type.
if ... then ... else is a convenient programming construct; the bits in between are statements not expressions.
In fact, the entire ternary construct is an expression, whereas the if ... then ... else is itself a statement. (You can assign a value to a ternary; e.g. foo = ... ? ... : ... but foo = if ... then ... else is meaningless and therefore syntatically invalid.)
Your code does not compile because the types of both sides of a conditional expression must match. There is no such requirement for the if statement - in fact, there is no requirement for the two parts to be related to each other in any way, let alone assigning the same variable.
Adding a cast fixes your conditional (demo on ideone):
K k = (1==1)?null:(K)createK();
Those operators are different.
1) In case of if ... then ... else there is no limitations of actions
2) Ternary operator. Using that both sides must have the same type.
You can fix everything with cast:
K k = (1 == 1) ? null : (K)(createK());
Using condition ? valueA : valueB operator is meaningful only if there is an Lvalue,
Lvalue = condition ? valueA : valueB
The exact meannig of this operator can be transformed to an if else statement:
int y,z;
int x= (y==0) ? y+1 : z+3
this ends up to be the same as:
if(y==0)
{
x=y+1;
}else
{
x = z+3;
}
I know that we cant use assignment operator in if statements in java as we use in any other few languages.
that is
int a;
if(a = 1) { }
will give a compilation error.
but the following code works fine, how?
boolean b;
if(b = true) { }
EDIT : Is this the exception to rule that assignment cant be used in if statement.
Because the "result" of an assignment is the value assigned... so it's still a boolean expression in the second case. if expressions require the condition to be a boolean expression, which is satisfied by the second but not the first. Effectively, your two snippets are:
int a;
a = 1;
if (a) { }
and
boolean b;
b = true;
if (b) { }
Is it clear from that expansion that the second version will compile but not the first?
This is one reason not to do comparisons with true and false directly. So I would always just write if (b) instead of if (b == true) and if (!b) instead of if (b == false). You still get into problems with if (b == c) when b and c are boolean variables, admittedly - a typo there can cause an issue. I can't say it's ever happened to me though.
EDIT: Responding to your edit - assignments of all kinds can be used in if statements - and while loops etc, so long as the overall condition expression is boolean. For example, you might have:
String line;
while ((line = reader.readLine()) != null)
{
// Do something with a line
}
While I usually avoid side-effects in conditions, this particular idiom is often useful for the example shown above, or using InputStream.read. Basically it's "while the value I read is useful, use it."
For if you need an expression that evaluates to boolean. b = true evalueates to boolean but a = 1 evaluates to int as assignments always evaluate to the assigned values.
The reason the second code works okay is because it is assigning 'b' the value of true, and then comparing to see if b is true or false. The reason you can do this is because you can do assignment operators inside an if statement, AND you can compare against a boolean by itself. It would be the same as doing if(true).
In java, you don't have implicit casting. So non-boolean values or not automatically transformed to booleans.
In the first case, the result of the statements is an int, which is non-boolean, which will not work. The last case, the result is boolean, which can be evaluated in an if-statement.
The rule is not that "assignment can't be used in an if statement", but that "the condition in an if statement must be of type boolean". An assignment expression produces a value of the type being assigned, so Java only permits assignment in an if statement if you're assigning a boolean value.
This is a good reason why the style if (foo == true) should be avoided, and instead simply write if (foo).