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 an 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)
get(bar.getFooClass(),
bar.hasLongId() ? bar.getFooId() : bar.getFooId().intValue());
And it still complained that I had provided a Long
. That seemed strange. Then I tried this:
get(bar.getFooClass(),
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
and
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?
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.
The values are getting unboxed due to rule #1 of Binary Numeric Promotion:
If any operand is of a reference type, it is subjected to unboxing conversion
What this means essentially, that when you have a ternary expression, the resulting type of the expression must be determinable statically (at compile time). The second and third operands must be coerced into a single type, which is the type of the expression. If both operands are a numeric type, binary numeric promotion kicks in to determine the final type of the expression.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With