Dana Dana - 10 months ago 43
Java Question

Compiler dropping my type conversion?

I'm puzzled by what I had to do to get this code to work. It seems as if the compiler optimized away a type conversion that I needed, or there's something else I don't understand here.

I have various objects that are stored in the database that implement the interface Foo. I have an object, bar, which holds data I'm using to retrieve my Foo objects. bar has these methods:

Class getFooClass()

Long getFooId()

I pass the class and ID to a method with this signature, which delegates to hibernate which retrieves the subject based on its class and ID:

public <T> T get(Class<T> clazz, Serializable id);

There are different implementers of Foo, and some of these hibernate objects have a Long id, and others have a Integer id. Although this method accepts either, farther down it had better have the right one. So when I tried to call get() on an object with an Integer id, as follows, I understandably got an error complaining that I had provided a Long where an Integer was required:

get(bar.getFooClass(), bar.getFooId());

There's no hibernate problem here, I just need to provide an Integer where an Integer id is required and a Long where a Long id is required. So I added a method to bar, hasLongId(), and tried this: (at this point you may be thinking this isn't a good design, but that's not my question right now)

bar.hasLongId() ? bar.getFooId() : bar.getFooId().intValue());

And it still complained that I had provided a Long. That seemed strange. Then I tried this:

bar.hasLongId() ? bar.getFooId()
: new Integer(bar.getFooId().intValue()));

Same error! How can this be? So I stepped through in the debugger, and yes, it stepped through intValue() and also through the Integer constructor, but then in the get method, the passed parameter was in fact a Long -- the same Long object that was returned from getFooId().

I don't understand what's happening, so I just try various things:

Integer intId = bar.getFooId().intValue();
get(bar.getFooClass(), bar.hasLongId() ? bar.getFooId() : intId);
// same error


Serializable id = bar.hasLongId() ? bar.getFooId()
: new Integer(bar.getFooId().intValue());
get(bar.getFooClass(), id);
// same error

And finally:

Serializable id;
if (bar.hasLongId()) {
id = bar.getFooId();
} else {
id = bar.getFooId().intValue();
get(bar.getFooClass(), id);

This one works. So apparently it has something to do with the ternary operator. But why? Can someone explain what happened here?

Answer Source

This is a great question and goes into the nitty gritty details of the semantics of the ternary expression. No, your compiler is not broken or playing tricks on you.

In this case, if the types of the second and third operands of the ternary expression is long and int, then the resulting type is always long. This is due to binary numeric promotion.

According to the JLS (Java Language Specification):

..., binary numeric promotion is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands.