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.
Related
Why this is a compile-time error when Java does the Autoboxing? Am I missing something?
int primitiveIntVariable = 0;
if (primitiveIntVariable instanceof Integer) {
}
I get
Inconvertible types; cannot cast 'int' to 'java.lang.Integer'
As the name suggests, instanceof means an instance (object) of a class. Primitive datatypes are not instances.
This is how you get the class for a primitive datatype:
int i = 1;
System.out.println(((Object)i).getClass().getName());
// prints: java.lang.Integer
So instead of instanceof, use isInstance(...) like this:
Integer.class.isInstance(1); // returns true
Integer.class.isInstance(1.2); // returns false
Hope this helps. Good luck.
int cannot be anything but an int, so the entire concept of using instanceof is meaningless, and misguided.
You only use instanceof if you need to check if the actual object is a particular type different from the type of the variable in question (or known supertypes hereof).
E.g. if the declared type of the variable (or the return value, or the compile-type of expression), is Integer, then it makes no sense whatsoever to check it is an instanceof of Integer. Given that Java is type-safe, you already know that it is.
Similarly, it makes no sense whatsoever to check if a known Integer is an instanceof of Number. You already know that it is.
And it makes even less sense to check if a known int is an instanceof of Integer. It's a primitive, and you know it is, so there is absolutely no way it can be an instance of any object type.
The last will generate a compiler error. The first two examples are compiler warnings, which is very evident if you use any good IDE. Always use a good IDE, because they catch so many dumb mistakes we all happen to write occasionally.
So the above was an explanation of why it makes no sense to even try, but even though integerVar instanceof Integer makes no sense, it compiles ok, but intVar instanceof Integer fails to compile, so why it that?
The reason is actually related to this mistaken statement in the question:
when Java does the Autoboxing
Java doesn't do autoboxing everywhere. Autoboxing only happens in:
assignment contexts, e.g. Integer x = 5
invocation contexts, e.g. foo(5) where parameter is an Integer
casting contexts, e.g. (Integer) 5 or (Object) 5
It does not happen by itself in the middle of an expression, and instanceof is an expression operator.
But, more specifically, it fails to compile because JLS 15.20.2. Type Comparison Operator instanceof says so:
RelationalExpression instanceof ReferenceType
The type of the RelationalExpression operand of the instanceof operator must be a reference type or the null type, or a compile-time error occurs.
This question already has answers here:
Generic type extending Number, calculations
(3 answers)
Closed 5 years ago.
The following code snippet throw me the error as shown in the header, I didn't figure out why it does not work as T is of type Number, I expected operator '+' to be fine.
class MathOperationV1<T extends Number> {
public T add(T a, T b) {
return a + b; // error: Operator '+' cannot be applied to 'T', 'T'
}
}
Would be appreciate if anyone can provide some clues, thx !
There is a fundamental problem with the implementation of this idea of generic arithmetic. The problem is not in your reasoning of how, mathematically speaking, this ought to work, but in the implications of how it should be compiled to bytecodes by the Java compiler.
In your example you have this:
class MathOperationV1<T extends Number> {
public T add(T a, T b) {
return a + b; // error: Operator '+' cannot be applied to 'T', 'T'
}
}
Leaving boxing and unboxing aside, the problem is that the compiler does not know how it should compile your + operator. Which of the multiple overloaded versions of + should the compiler use? The JVM has different arithmetic operators (i.e. opcodes) for different primitive types; hence the sum operator for integers is an entirely different opcode than the one for doubles (see, for example, iadd vs dadd) If you think about it, that totally makes sense, because, after all, integer arithmetic and floating-point arithmetic are totally different. Also different types have different sizes, etc (see, for example ladd). Also think about BigInteger and BigDecimal which extend Number as well, but those do not have support for autoboxing and therefore there is no opcode to deal with them directly. There are probably dozens of other Number implementations like those in other libraries out there. How could the compiler know how to deal with them?.
So, when the compiler infers that T is a Number, that is not sufficient to determine which opcodes are valid for the operation (i.e. boxing, unboxing and arithmetic).
Later you suggest to change the code a bit to:
class MathOperationV1<T extends Integer> {
public T add(T a, T b) {
return a + b;
}
}
And now the + operator can be implemented with an integer sum opcode, but the result of the sum would be an Integer, not a T, which would still make this code invalid, since from the compiler standpoint T can be something else other than Integer.
I believe there is no way to make your code generic enough that you can forget about these underlying implementation details.
--Edit--
To answer your question in the comment section consider the following scenario based on the last definition of MathOperationV1<T extends Integer> above.
You're correct when you say that the compiler will do type erasure on the class definition, and it will be compiled as if it was
class MathOperationV1 {
public Integer add(Integer a, Integer b) {
return a + b;
}
}
Given this type erasure it would seem as if using a subclass of Integer ought to work here, but that's not true since it would make the type system unsound. Let me try to demonstrate that.
The compiler cannot only worry for the declaration site, it also has to consider what happens in the multiple call sites, possibly using a different type argument for T.
For example, imagine (for the sake of my argument) that there is a subclass of Integer that we'll call SmallInt. And assume our code above compiled fine (this is actually you question: why it doesn't compile?).
What would happen then if we did the following?
MathOperationV1<SmallInt> op = new MathOperationV1<>();
SmallInt res = op.add(SmallInt.of(1), SmallInt.of(2));
And as you can see the result of the op.add() method is expected to be a SmallInt, not an Integer. However, the result of our a + b above, from our erased class definition, would always return an Integer not a SmallInt (because the + uses the JVM integer arithmetic opcodes), and therefore this result would be unsound, right?.
You may now wonder, but if the type erasure of MathOperationV1 always returns an Integer, how in the world in the call site it might expect something else (like SmallInt) anyways?
Well, the compiler adds some extra magic here by casting the result of add to a SmallInt, but only because it has already ensured that the operation can't return anything else other than the expected type (this is why you see a compiler error).
In other words, your call site would look like this after erasure:
MathOperationV1 op = new MathOperationV1<>(); //with Integer type erasure
SmallInt res = (SmallInt) op.add(SmallInt.of(1), SmallInt.of(2));
But that would only work if you could ensure that add returns always a SmallInt (which we cannot due to the operator problems described in my original answer).
So, as you can see, your type erasure just ensures that, as per the rules of subtyping, you can return anything that extends an Integer, but once your call site declares a type argument for T, you're supposed to always assume that same type wherever T appeared in the original code in order to keep the type system sound.
You can actually prove these points by using the Java decompiler (a tool in your JDK bin directory called javap). I could provide finer examples if you think you need them, but you would do well to try it yourself and see what's happening under the hood :-)
Auto(un)boxing only works for types that can be converted to their primitive equivalents. Addition is only defined for numeric primitive types plus String. i.e: int, long, short, char, double, float, byte . Number does not have a primitive equivalent, so it can't be unboxed, that's why you can't add them.
+ isn't defined for Number. You can see this by writing (with no generics):
Number a = 1;
Number b = 2;
System.out.println(a + b);
This simply won't compile.
You can't do addition generically directly: you need a BiFunction, a BinaryOperator, or similar, which is able to apply the operation to the inputs:
class MathOperationV1<T extends Number> {
private final BinaryOperator<T> combiner;
// Initialize combiner in constructor.
public T add(T a, T b) {
return combiner.apply(a, b);
}
}
But then again, you may as well just use the BinaryOperator<T> directly: MathOperationV1 adds nothing over and above that standard class (actually, it provides less).
I am creating a simple wrapper class for numbers. Simply put, I want it to display the value 42 verses 42.0; however, it should display the value 1.6180338 as that number. Simply enough.
Code
private double number;
...
#Override
public String toString() {
return String.valueOf(
number == longValue()
? longValue()
: number );
}
...
#Override
public long longValue() {
return (long) number;
}
Issue
The problem is that the value of 42.0 is always displayed Not the correct 42 value in the toString(...) method
My Thoughts
Although the String.valueOf(...) method has a lot of overloaded methods to display the correct primitive values as strings, there is ambiguity in which overloaded method to use. It can use String.valueOf(double) or String.valueOf(long). This is because of the ternary operator statement and resulting result type.
I thought that the compiler would be able to discern the long type and call the appropriate String.valueOf(long) method. That appears to not be the case; instead, the JVM will choose at compile time the safest, yet most-confined overloaded method. In this case, that is String.valueOf(double) because it can safely convert a long to a double.
Question
I know this isn't possible in Java right now, but is something like this available in other languages currently? And is there some kind of definition that explains this method, and can you explain it in more detail?
I mean a definition like Covariance or Contra-variance. Note: I realize that the definition is not one of those two :)
As Java is statically typed language, the result of the ternary operator must have an explicit type, defined during the compilation, so the compilator can continue handling the outer expression. As the both branches of ternary are numbers, they are promoted to the more precise type as described in JLS 15.25 and 5.6.2. You can work-around this casting the arguments to the object:
return String.valueOf(
number == longValue()
? (Object)longValue()
: (Object)number );
This way you will box the numbers and use String.valueOf(Object) which works nice for both branches.
In some code I see this:
private void compute(Long a, Long b, Long c) {
long result = a-(b+c);
...
It seems a bit strange that the result is stored in a primitive long instead of a Long object corresponding to its operands.
Are there any reason that a result should be stored as a primitive?
It seems a bit strange that the result is stored in a primitive long instead of a Long object corresponding to its operands.
No, what is "strange" is that you can use the + and - operators on Long objects. Before Java 5, this would have been a syntax error. Then autoboxing/unboxing was introduced. What you're seeing in this code is autounboxing: the operators require primtives, so the compiler automatically inserts a call to longValue() on the objects. The arithmetic is then performed on primitive long values, and the result is also a long that can be stored without further conversion on the variable.
As for why the code does this, the real question is why someone would use the Long type instead of long. Possible reasons:
The values come from some library/API that delivers Long values.
The values are stored in collections (List, Map), which cannot hold primitives.
Sloppiness or cargo cult programming.
The ability to have null values is required, e.g. to signal unavailable or uninitialized data.
Note that the ability of Long to hold null values means that the calculation (or more specifically, the longValue() calls inserted by the compiler) can fail with a NullPointerException - a possibility the code should deal with somehow.
The reason is obvious: result is declared as primitive.
The arithmetic operators + and - are not defined for boxed types (e.g. Long) but for primitive types (e.g. long).
The result is also a long. See Autoboxing and Unboxing tutorial
Autoboxing this into a Long would result in a small performance cost. It is also unnecessary because
We know it will be non-null (if a, b or c were null, a NullPointerException would occur).
It would be autoboxed implicitly if we use it later where a Long is required.
Based on your needs.I mean the decelaration.
Autoboxing and unboxing can happen anywhere where an object is expected and primitive type is available
Usually you should prefer using primitives, especially if you are certain they cannot be null. If you insist on using the boxed types always think extra hard about what happens when it is null.
Java will do the boxing and unboxing automatically for you, but staring at an int and wondering why you got a NullPointerException can be fun.
From Java 1.5 onwards, autoboxing and unboxing occurs implicitly whenever needed.
The following line:
long result = a-(b+c);
...asks Java to take the result of the expression using 3 Longs, and then store it in a primitive long. Before Java 5, it would complain about the types not matching - but these days it just assumes you mean what you say and automatically does the conversion from object to primitive type for you.
In this example however, unless there's some other good reason not presented here, there's absolutely no point having the parameters as the boxed, object type in the first place.
As per the javadoc
Boxing conversion converts expressions of primitive
type to corresponding expressions of reference type.
Specifically, the following nine conversions are called the boxing conversions:
From type boolean to type Boolean
From type byte to type Byte
From type short to type Short
From type char to type Character
From type int to type Integer
From type long to type Long
From type float to type Float
From type double to type Double
From the null type to the null type
Ideally, boxing a given primitive value p, would always yield an identical reference.
In practice, this may not be feasible using existing implementation techniques. The
rules above are a pragmatic compromise. The final clause above requires that certain
common values always be boxed into indistinguishable objects. The implementation may
cache these, lazily or eagerly. For other values, this formulation disallows any
assumptions about the identity of the boxed values on the programmer's part. This would
allow (but not require) sharing of some or all of these references.
This ensures that in most common cases, the behavior will be the desired one, without
imposing an undue performance penalty, especially on small devices. Less memory-limited
implementations might, for example, cache all char and short values, as well as int and
long values in the range of -32K to +32K.`
Here is the Oracle Doc source
The answer for your doubt is
autoboxing and autounboxing in Java which converts from primitive to wrapper class objects and vice versa respectively.
autoboxing means internally compiler uses valueOf() method of primitive classes ans autounboxing means internally compiler uses xxxValue() method.
Suppose for
private void compute(Long a, Long b, Long c) {
long result = a-(b+c);
its makes this conversion a.longValue()-(b.longValue()+c.longValue())
Which means even before your statement performs addition the compiler provides the primitives of long type as input to your operands
Remember that this goes in hand as JAVA is statically and strongly typed language.
Hence you get long type output
I hope i cleared your doubt
Sorry to keep asking the basics but I don't understand this simple code and why the first print statement goes through the compiler ok and even prints true, but the second print statement doesn't compile, giving me an "incomparable types" error:
int in1 = 38;
Number Nn1 = in1;
System.out.println(in1 == Nn1);
System.out.println(Nn1 == in1);
I am not expecting this result, I thought it was pretty standard that == was symmetric?
I am using javac 1.6.0_26 and also NetBeans but get the same result, the first println statement compiles without problem and the second does not..
I believe that, according to the Java Language Specification, neither way round should compile.
It's important firstly to understand that auto(un)boxing is only applied to expressions that meet certain criteria, and only for specific wrapper classes (Integer, Long etc, not Number).
Now, in the case of ==, autounboxing is applied specifically when one is of
[primitive] numeric type and the other is convertible to [primitive] numeric type (JLS 15.12.1) according to the rules. And as we've just stated, "according to the rules", Number is not convertible to a numeric primitive type.
It is NOT, the case, for example, that the int should be converted to an Integer and then a reference comparison made: autoboxing is not specified to be applied to an == reference comparison (JLS 15.21.3).
So if your compiler is allowing the cited code to compile, it does not obey the Java Language Specification.
This behaviour makes sense because to perform a numeric comparison, the compiler needs to know the actual specific type of both operands in order to perform numeric promotion. You might think that you can compare, say, a Number with an integer, and that the compiler should just call .intValue() on the Number. But this is inappropriate, because if the original number type was actually a Float, then the correct comparison is actually to first convert the integer to a Float rather than the other way round. In other words, with a Number, the compiler doesn't have all the information to correctly perform a numeric comparison with a primitive.
My compiler (jdk1.7.0_03 on Windows) says that both lines are incorrect:
Operator == cannot be applied to int and java.lang.Number
When you check for equality between an int and an Integer, unboxing occurs.
In fact, compiler is aware that Integer operand wraps only int. It's like a clue.
However, Number, although implemented by Integer (and others), is too generic and would expect too much job for compiler to extract the original primitive type in order to operate the unboxing.
Hence, compiler complains about it and expects you a more fine-grained type.
Both lines are a compile error. If not, there's a bug in NetBeans.
If you change Number to Integer, both lines compile.
int in1 = 38;
Integer Nn1 = in1; // Changed to Integer
System.out.println(in1 == Nn1); // compiles
System.out.println(Nn1 == in1); // compiles
Because you are comparing values reference-type values with primitive values, the only way it could work would be because of an auto unboxed conversion. But that type of conversion doesn't seem to be specified in the Java Language Specification.
It probably not symmetric because it's not intended to be possible at all. Maybe a compiler bug.