overloaded method call ambiguity with ternary operator - java

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.

Related

Operator '+' cannot be applied to 'T', 'T' for bounded generic type [duplicate]

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

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 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.

function ambiguity in java

in java, i'm facing a function ambiguity. basically i'm overloading a variadic function
i'm defining function like
static void f(Integer... a)
{
// .. some statements
}
static void f(float f,Integer... a)
{
// .. some other statements
}
can calling the function with following function calls
f(1,2);
f(1.2f,1,2);
and this error message pops up
error: reference to f is ambiguous, both method f(Integer...) in Test and method f(float,Integer...) in Test match
f(1,2);
^
can someone help me to understand if i'm missing any basic concept in java here. thnx ..
both method can take the first parameters you've entered f(1,2); and that's why you get ambiguity.
if you'll do
f((float)1,2);
you won't get the error for example
In Java language, int values can be automatically casted to float values (The opposite is not allowed).
Therefore, when you make a call to
f(1,2)
Java compiler matches all possible signatures that automatic type-conversions allow, i.e:
f(int, int)
f(float, int)
f(float, float)
f(int, float)
f(int, ...)
f(float, ...)
There resides the ambiguity for which the compiler does not know if you meant to call f(int, ...) or f(float, float).
When several methods are applicable, the compiler tries to find the most specific one. If two methods are maximally specific, there is an ambiguity and you get an error.
In summary (the actual rules are a little more complicated):
the compiler determines that for f(1, 2), both methods are applicable (1 can be an integer or a float)
the compiler then needs to determine which method is more specific: in your case, none is more specific in the sense defined in the specifications because there is no relationship between float and Integer (examples: if you had a f(int i, Integer... j) vs. f(float f, Integer... j), the former would be more specific because int is more specific than float among primitives. Similarly, if you had a f(Number f, Integer... i) vs. a f(Integer... i), the latter would be more specific because Integer extends Number).
The runtime can choose to cast the function with the list of integers to call either function. That's where the confusion lies. Any list of integers you supply can also be turned into another call with the single leading float followed by a list of integers.

importance of ordering of operands in Java's ==

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.

Java Integer vs. String Autoboxing Design

In Java, I can do the following to succinctly guard against a NullPointerException:
if ("myString".equals(someOtherString))
But I cannot do the same with Integers, e.g.
if (5.equals(someOtherInteger))
I get a compile-time error. Any ideas on why this design decision was made? Or any resources that might explain it? Thanks in advance.
EDIT: someOtherInteger is an Integer, not an int.
String has always been an object in Java. There is no autoboxing for strings, and there can't be in principle. Autoboxing from the primitive int to the Integer object has been introduced fairly recently.
It is valid to ask why trying to access member variables of primitives doesn't invoke autoboxing (95.toString(radix) would actually be pretty convenient), but I imagine that the reason is that it wasn't considered a likely use-case, since since almost every wrappedPrimitive.method() has an equivalent WrapperClass.method( primitive ) version.
equals() is usually unnecessary for primitive types since == is already there. However, you do make a good case for it as a null-guard... 5 == integerInstance will try to unbox the instance, and throw a NullPointerException if the instance is null, unfortunately. (I didn't fully appreciate your point at first.)
That said, it would be really cool if we could hear from someone working on Java either currently or at the introduction of autoboxing about whether they considered this sort of functionality.
The JLS specifies that boxing conversions can only occur during assignment conversions, method invocation conversions, or casting conversions. Since you are neither assigning 5 to a variable, passing it as an argument to a method, nor explicitly casting it to Integer, it will not be autoboxed for you.
Assignment conversion (§5.2, §15.26) converts the type of an
expression to the type of a specified variable.
Assignment conversion may cause an OutOfMemoryError (as a result of
boxing conversion (§5.1.7)), a NullPointerException (as a result of
unboxing conversion (§5.1.8)), or a ClassCastException (as a result of
an unchecked conversion (§5.1.9)) to be thrown at run-time.
Method invocation conversion (§5.3, §15.9, §15.12) is applied to each
argument in a method or constructor invocation and, except in one
case, performs the same conversions that assignment conversion does.
Method invocation conversion may cause an OutOfMemoryError (as a
result of boxing conversion (§5.1.7)), a NullPointerException (as a
result of unboxing conversion (§5.1.8)), or a ClassCastException (as a
result of an unchecked conversion (§5.1.9)) to be thrown at run-time.
Casting contexts allow the use of one of:
...
a boxing conversion (§5.1.7) optionally followed by a widening
reference conversion (§5.1.5)
you can use
if (someOtherInteger!=null && someOtherInteger == 5)
I suspect that autoboxing is not implemented for the literal 5, whereas it is for a string myString, as a safety measure. It's safe to autobox a syntactic structure that is prepended and appended with double quotation marks "", because it's unlikely that the quotation marks are unintended, so the user's intention is clear and type-safety is not compromised.
However, the literal 5 could be a typo on the user's part - or it could be intended to be a string, rather than an integer. Therefore, to maintain the benefit that variables must be declared before use in object-oriented programming in order to prevent typos (among many other advantages) (even if it's implicit, as in the case of autoboxing), 5 is not autoboxed.
Here is a bit of reading on the different comparisons:
http://www.leepoint.net/notes-java/data/expressions/22compareobjects.html
Not sure if it was a built in design to reject int
If you do
Integer s=5;
Integer d=5;
if(d.equals(s)){
System.out.println("Fun");
}
It works just fine.
int is a primitive type it doesn't support any methods itself. To compare 2 ints you simply use the == convention as in:
if(a == b)
There is an Integer class that is a wrapper for an int that supports some other method calls
Edit:
Based on your edit you want to compare to Integer but the problem is the literal 5 isn't an Integer you have to create a new integer for it.
Integer myInt = 5;
if(myInt.equals(someOtherInteger)) ...
This design is inherent in the fact that primitives don't have any methods. As to whether primitives should support methods (or simply not exist) is integral to the debate as to whether Java is a Pure Object Oriented Language or not (many say no due to the fact that primitives exist).

Categories

Resources