Compiler dropping my type conversion?
Asked Answered
M

1

8

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?

Milson answered 21/9, 2016 at 0:12 Comment(4)
There's not enough information. Don't paraphrase the code and don't summarize the error messages. I might or might not be able to figure out what's wrong based on what you gave us, but I'm not going to spend much time on it because I can tell that you haven't given us anything complete.Interlink
Not related to your question, but why don't you make getFooId() return a Number instead of the extra method and type conversion?Sestos
Voting to reopen because the other question doesn't discuss ternary expressions or boxed values.Sestos
Agree on @shmosel, the originally marked-as-duplicated question is different. So I reopened this questionThor
B
10

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.

Benton answered 21/9, 2016 at 0:30 Comment(3)
What about the explicitly boxed type? Is it getting unboxed? Why?Sestos
Good answer. It will be more complete, as @Sestos mentioned, to tell why the compiler, when encounter ternary operator with condition ? Long : int or condition? Long : Integer, is deciding to auto-unbox the Long to form condition ? long : int. The form condition? Long : Integer should also be valid here?Thor
Edited to answer the questions above.Benton

© 2022 - 2024 — McMap. All rights reserved.