Java autoboxing and ternary operator madness
Asked Answered
L

4

24

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 ;)

Liberalism answered 21/8, 2014 at 2:36 Comment(3)
none is giving me a NPE, please check the codeGrounds
@Chechus It gives to me.Losing
Ternary operator is weird for sure. Have you also noticed how in Java for an if or while loop the condition mandates wrapping in parentheses. Yet on condition of ternary it's not required. I got tripped up on a similar issue today - down to assumption autoboxing would occur. #69552517Feldstein
D
35

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.

Decipher answered 21/8, 2014 at 2:46 Comment(4)
Well after a test, with java 1.8 the result es different (returns a null, no NPE). Ideas?Grounds
@Chechus: Java8's type inference is a lot better. That's a side effect of the lambda expression support. My guess is that the type of the ternary expression is now being influenced by the type of the receiver (i.e. it's being assigned to an Integer, so let's make the type of the expression Integer and stop trying to unbox the null)Decipher
@skaffman: it seems to be a side-effect of implementing the better type inference, but by reading the specification I got the feeling, you should not rely on it as “numeric conditional expressions” are explicitly stated to be no “poly expressions”, read, not affected by the target type.Gromwell
actually, this might be a javac bug; and the spec is probably bad too. see the very last example in stackoverflow.com/a/33182091Centralize
D
13

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.

Dromedary answered 21/8, 2014 at 2:48 Comment(0)
I
4

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.

Irons answered 21/8, 2014 at 2:50 Comment(0)
H
3
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.

Hamper answered 21/8, 2014 at 2:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.