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.
Related
This question already has answers here:
Java conditional operator ?: result type
(5 answers)
Closed 6 years ago.
I'm getting nullpointer exception if I use ternary operator.
Integer val = null;
Object res = val == null ? val : val.intValue();
But not with if else
Integer val = null;
Object res;
if( val == null ) {
res = val;
} else {
res = val.intValue();
}
Can anyone please explain why?
Thanks
Sudar
The behavior you encountered results from the rules of determining the type of the ternary conditional expression.
In your case, the type of the expression
val == null ? val : val.intValue();
is int.
This is specified by JLS 15.25. :
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.
Your second operand is Integer and your third operand is int, therefore the type of the expression is int.
Therefore, when val == null, val is un-boxed (i.e. val.intValue() is called for a null value) and a NullPointerException is thrown.
In your if-else expression val is not un-boxed when its value is null (since you assign it to an Object variable, so there's no NullPointerException.
That said, since you are assigning an Integer variable to an Object variable, your conditions in either of the snippets are pointless (since assigning an int to an Object variable simply boxes the int back to Integer).
You can simply assign
Object res = val;
and get the same end result without the exception.
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.
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.
Below is the code snippet , where I am confused .The return type of method is int , so the method can not return null.But , when I uncomment 1st return statement and comment 2nd return statement, method's doesn't show any error not even warning.
I am confused what is the reason behind that.I am using java version 7.
protected int calculateLogicalPageRangeForTitles(String logicalpage) throws Exception {
//return StringUtils.isNumeric(logicalpage) ? Integer.parseInt(logicalpage) : null;
return null;
}
The code is legal and compiles.
See Java Spec:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.25
If one of the second and third operands is of the null type and the
type of the other is a reference type, then the type of the
conditional expression is that reference type.
This means, the result of the ?: will be an Integer, which will be unboxed to int. This unboxing will throw a NullPointerException.
This statement only returns a int. parseInt returns an int and 0 is an int
return StringUtils.isNumeric(logicalPage) ? Integer.parseInt(logicalPage) : 0;
The first return statement
return StringUtils.isNumeric(logicalPage) ? Integer.parseInt(logicalPage) : 0;
always returns an int value (or an exception). The method Integer.parseInt() returns a primitive int value as well. So no errors or warnings are shown.
The second statement, however,
return null;
tries to return a null value for the primitive return type int, which is not valid and thus leads to a compiler error.
That's because parseInt returns an int (primitive type), which matches your method's signature.
null, on the other hand, is not compatible with a primitive int, that's why you have an error.
Because your first statement will always return an int. So it compiles.
But second statement returns null which is of Object type. This will compile if your return type is Integer instead of int
int is a primitive... therefore it has to have a value. null is a value telling us that the reference (to a class) does not refer to any instance.
Your first line is not valid either. if it turns out to be false and it tries to return the null value, an exception is raised.
If you write the code out to a verbose if statement it will give you the error. Like:
if (StringUtils.isNumeric(logicalpage){
return Integer.parseInt(logicalpage);
}else{
return null;
}
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.