This question already has answers here:
Unexpected type resulting from the ternary operator
(4 answers)
Closed 4 years ago.
I was playing with ternary operator and noticed something odd. I have code below:
class Main {
static void foo(int a){
System.out.println("int");
}
static void foo(String a){
System.out.println("String");
}
static void foo(Object a){
System.out.println("object");
}
public static void main(String[] args) {
foo(2==3 ? 0xF00:"bar");
System.out.println((2==3 ? 0xF00:"bar").getClass().getName());
}
}
Which results in
object
java.lang.String
First line of result shows that this instruction passed to foo method with object parameter.
Second line that the instruction itself results in String.
Question:
Why if result is String compiler decides to go with Object?
Is this because of the type ambiguity?
If yes then why getting class name returned java.lang.String?
In Java, you have compile time type information and you have run time type information. Compile time type information is what the compiler can deduce about the type of a value or expression just by looking at it, but without executing it. When the compiler sees the expression
2 == 3 ? 0xF00 : "bar"
It does not know whether 2 == 3 will be true or false, because it does not execute code. So all it knows is that the result can be an Integer, or a String. So when the time comes to pick which foo method to call, it picks the one that accepts Object, since that is the only one that it knows will work in both scenarios.
However, when the code is actually running, 2 == 3 will be false, and the result will be a String, whose getClass() method will return String.class. And that is what you need to note: getClass() does not return the type that the variable had at compile time, but it returns the actual type of the object that the variable holds at run time. i.e.
Object o = "Hello!";
System.out.println(o.getClass());
will print java.lang.String, because even though to the compiler it is an Object, at run time it is actually a String.
You can skip to Summary if not interested in reading.
Refer the JavaDoc here: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25
Under 15.25.3. Reference Conditional Expressions It says:
The type of the conditional expression is the
result of applying capture conversion
For capture conversion : https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.10
Summary:
For type determination, capture conversion is used, wherein for your example int is first boxed to Integer and then the closest common super class of Integer and String is fetched, which is Object class. So the type for the Conditional Expression is Object and so the method with Object as parameter is called.
Now for second part, the Conditional operator is evaluated first, then it is unboxed and then .getClass() is evaluated. So it prints java.lang.String.
This is also documented here: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25
Under 15.25. Conditional Operator ? :
At run time, the first operand expression of the conditional
expression is evaluated first. If necessary, unboxing conversion is
performed on the result.
At compile stage, the compiler noticed that the result of 2 == 3 ? 0xF00 : "bar" could be int or String. To be compatible with both, it decide to call foo(Object a).
At runtime, the result of 2 == 3 ? 0xF00 : "bar" is String bar.
Related
Consider the following code
public class JDK10Test {
public static void main(String[] args) {
Double d = false ? 1.0 : new HashMap<String, Double>().get("1");
System.out.println(d);
}
}
When running on JDK8, this code prints null whereas on JDK10 this code results in NullPointerException
Exception in thread "main" java.lang.NullPointerException
at JDK10Test.main(JDK10Test.java:5)
The bytecode produced by the compilers is almost identical apart from two additional instructions produced by the JDK10 compiler which are related to autoboxing and seem to be responsible for the NPE.
15: invokevirtual #7 // Method java/lang/Double.doubleValue:()D
18: invokestatic #8 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
Is this behaviour a bug in JDK10 or an intentional change to make the behaviour stricter?
JDK8: java version "1.8.0_172"
JDK10: java version "10.0.1" 2018-04-17
I believe this was a bug which seems to have been fixed. Throwing a NullPointerException seems to be the correct behavior, according to the JLS.
I think that what is going on here is that for some reason in version 8, the compiler considered the bounds of the type variable mentioned by the method's return type rather than the actual type arguments. In other words, it thinks ...get("1") returns Object. This could be because it's considering the method's erasure, or some other reason.
The behavior should hinge upon the return type of the get method, as specified by the below excerpts from §15.26:
If both the second and the third operand expressions are numeric expressions, the conditional expression is a numeric conditional expression.
For the purpose of classifying a conditional, the following expressions are numeric expressions:
[…]
A method invocation expression (§15.12) for which the chosen most specific method (§15.12.2.5) has a return type that is convertible to a numeric type.
Note that, for a generic method, this is the type before instantiating the method's type arguments.
[…]
Otherwise, the conditional expression is a reference conditional expression.
[…]
The type of a numeric conditional expression is determined as follows:
[…]
If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.
In other words, if both expressions are convertible to a numeric type, and one is primitive and the other is boxed, then the result type of the ternary conditional is the primitive type.
(Table 15.25-C also conveniently shows us that the type of a ternary expression boolean ? double : Double would indeed be double, again meaning unboxing and throwing is correct.)
If the return type of the get method wasn't convertible to a numeric type, then the ternary conditional would be considered a "reference conditional expression" and unboxing wouldn't occur.
Also, I think the note "for a generic method, this is the type before instantiating the method's type arguments" shouldn't apply to our case. Map.get doesn't declare type variables, so it's not a generic method by the JLS' definition. However, this note was added in Java 9 (being the only change, see JLS8), so it's possible that it has something to do with the behavior we're seeing today.
For a HashMap<String, Double>, the return type of get should be Double.
Here's an MCVE supporting my theory that the compiler is considering the type variable bounds rather than the actual type arguments:
class Example<N extends Number, D extends Double> {
N nullAsNumber() { return null; }
D nullAsDouble() { return null; }
public static void main(String[] args) {
Example<Double, Double> e = new Example<>();
try {
Double a = false ? 0.0 : e.nullAsNumber();
System.out.printf("a == %f%n", a);
Double b = false ? 0.0 : e.nullAsDouble();
System.out.printf("b == %f%n", b);
} catch (NullPointerException x) {
System.out.println(x);
}
}
}
The output of that program on Java 8 is:
a == null
java.lang.NullPointerException
In other words, despite e.nullAsNumber() and e.nullAsDouble() having the same actual return type, only e.nullAsDouble() is considered as a "numeric expression". The only difference between the methods is the type variable bound.
There's probably more investigation that could be done, but I wanted to post my findings. I tried quite a few things and found that the bug (i.e. no unboxing/NPE) seems to only happen when the expression is a method with a type variable in the return type.
Interestingly, I've found that the following program also throws in Java 8:
import java.util.*;
class Example {
static void accept(Double d) {}
public static void main(String[] args) {
accept(false ? 1.0 : new HashMap<String, Double>().get("1"));
}
}
That shows that the compiler's behavior is actually different, depending on whether the ternary expression is assigned to a local variable or a method parameter.
(Originally I wanted to use overloads to prove the actual type that the compiler is giving to the ternary expression, but it doesn't look like that's possible given the above difference. It's possible there's still another way that I haven't thought of, though.)
JLS 10 doesn't seem to specify any changes to the conditional operator, but I have a theory.
According to JLS 8 and JLS 10, if the second expression (1.0) is of type double and the third (new HashMap<String, Double>().get("1")) is of type Double, then the result of the conditional expression is of type double. The JVM in Java 8 seems to be smart enough to know that, because you're returning a Double, there's no reason to first unbox the result of HashMap#get to a double and then box it back to a Double (because you specified Double).
To prove this, change Double to double in your example, and a NullPointerException is thrown (in JDK 8); this is because the unboxing is now occuring, and null.doubleValue() obviously throws a NullPointerException.
double d = false ? 1.0 : new HashMap<String, Double>().get("1");
System.out.println(d); // Throws a NullPointerException
It seems that this was changed in 10, but I can't tell you why.
The sample code is :
public class OverloadingTest {
public static void test(Object obj){
System.out.println("Object called");
}
public static void test(String obj){
System.out.println("String called");
}
public static void main(String[] args){
test(null);
System.out.println("10%2==0 is "+(10%2==0));
test((10%2==0)?null:new Object());
test((10%2==0)?null:null);
}
And the output is :
String called
10%2==0 is true
Object called
String called
The first call to test(null) invokes the method with String argument , which is understandable according to The Java Language Specification .
1) Can anyone explain me on what basis test() is invoked in preceding calls ?
2) Again when we put , say a if condition :
if(10%2==0){
test(null);
}
else
{
test(new Object());
}
It always invokes the method with String argument .
Will the compiler compute the expression (10%2) while compiling ? I want to know whether expressions are computed at compile time or run time . Thanks.
Java uses early binding. The most specific method is chosen at compile time. The most specific method is chosen by number of parameters and type of parameters. Number of parameters is not relevant in this case. This leaves us with the type of parameters.
What type do the parameters have? Both parameters are expressions, using the ternary conditional operator. The question reduces to: What type does the conditional ternary operator return? The type is computed at compile time.
Given are the two expressions:
(10%2==0)? null : new Object(); // A
(10%2==0)? null : null; // B
The rules of type evaluation are listed here. In B it is easy, both terms are exactly the same: null will be returned (whatever type that may be) (JLS: "If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression."). In A the second term is from a specific class. As this is more specific and null can be substituted for an object of class Object the type of the whole expression is Object (JLS: "If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.").
After the type evaluation of the expressions the method selection is as expected.
The example with if you give is different: You call the methods with objects of two different types. The ternary conditional operator always is evaluated to one type at compile time that fits both terms.
JLS 15.25:
The type of a conditional expression is determined as follows:
[...]
If one of the second and third operands is of the null type and the type of the other
is a reference type, then the type of the conditional expression is that reference
type.
[...]
So the type of
10 % 2 == 0 ? null : new Object();
is Object.
test((10%2==0)?null:new Object());
Is the same as:
Object o;
if(10%2==0)
o=null;
else
o=new Object();
test(o);
Since type of o is Object (just like the type of (10%2==0)?null:new Object()) test(Object) will be always called. The value of o doesn't matter.
Your answer is : Runtime because in runtime specify parameter is instance of String or not so in compile-time can't find this.
This is the really nice question.
Let me try to clarify your code that you have written above.
In your first method call
test(null);
In this the null will be converted into string type so calling the test(String obj), as per JLS you are convinced with the call.
In the second method call
test((10%2==0)?null:new Object());
Which is going to return the boolean "true" value. So first boolean "true" value is going to auto cast into Boolean Wrapper class object. Boolean wrapper Object is finding the best match with your new Object() option in the ternary operator. And the method calls with Object as a parameter so it calls the following method
public static void test(Object obj)
For the experiment sake you can try the following combinations then you will get better clarity.
test((10 % 2 == 0) ? new Object() : "stringObj" );
test((10 % 2 == 0) ? new Object() : null );
test((10 % 2 == 0) ? "stringObj" : null );
Finally in the last when you are calling with the following code.
test((10%2==0)?null:null);
This time again it returns as boolean "true" value, and it will again follow the same casts as explained above. But this time there is no new Object() parameter is there in your ternary operator. So it will be auto type cast into null Object. Again it follows same method call as the your first method call.
In the last when you asked for code if you put in if .. else statement. Then also the compiler doing the fair decision with the code.
if(10%2==0) {
test(null);
}
Here all the time your if condition is true and calling this code test(null). Therefore all the time it call the firsttest(String obj) method with String as parameter as explained above.
I think your problem is that you are making the wrong assumption, your expressions:
test((10%2==0)?null:new Object());
and
test((10%2==0)?null:null;
Will always call test(null), and that's why they will go through test (Object).
as #Banthar mentionend the ?: operator assigns a value to a variable first then evaluates the condition.
On the other hand, the if condition you mentioned always returns true, so the compiler will replace the whole if-else block with only the body of the if.
1) the test() method is determined by the type of the parameter at the compilation time :
test((Object) null);
test((Object)"String");
output :
Object called
Object called
2) The compiler is even smarter, the compiled code is equivalent to just :
test(null);
you can check the bytecode with javap -c:
0: aconst_null
1: invokestatic #6 // Method test:(Ljava/lang/String;)V
4: return
This is what Java Language Specifications say about the problem.
If more than one method declaration is both accessible and applicable
to a method invocation, it is necessary to choose one to provide the
descriptor for the run-time method dispatch. The Java programming
language uses the rule that the most specific method is chosen.
This is test(String) method in your case.
And because of that if you add...
public static void test(Integer obj){
System.out.println("Ingeter called");
}
it will show compilation error -The method test(String) is ambiguous for the type OverloadingTest.
Just like JLS says:
It is possible that no method is the most specific, because there are
two or more maximally specific methods. In this case:
If all the maximally specific methods have the same signature, then:
If one of the maximally specific methods is not declared abstract, it
is the most specific method. Otherwise, all the maximally specific
methods are necessarily declared abstract. The most specific method is
chosen arbitrarily among the maximally specific methods. However, the
most specific method is considered to throw a checked exception if and
only if that exception is declared in the throws clauses of each of
the maximally specific methods. Otherwise, we say that the method
invocation is ambiguous, and a compile-time error occurs.
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.
I have some string with Groovy expression. I need:
Execute Groovy expression that is contained in java.lang.String
Get result of the expression as an Object
Determine type of the resulting object
Is it possible? Also, if I won't use dynamic features of Groovy, will I be able to determine type of the expression "statically", i.e. in compile-time without executing expression itself.
Thanks
A very simple and easy way is to use the Eval class. Evaluates the specified String expression and returns the result. The type result of the expression is Object.
def object = Eval.me('1 + 1')
You can get the class of an object with the getClass() method or simply:
assert object.class == Integer
In this example, is not possible determine the static type checking at compilation time.
#groovy.transform.TypeChecked
void test() {
Integer object = Eval.me('1 + 1')
assert object.class == Integer
}
[Static type checking] - Cannot assign value of type java.lang.Object to variable of type java.lang.Integer
Let's look at the simple Java code in the following snippet:
public class Main {
private int temp() {
return true ? null : 0;
// No compiler error - the compiler allows a return value of null
// in a method signature that returns an int.
}
private int same() {
if (true) {
return null;
// The same is not possible with if,
// and causes a compile-time error - incompatible types.
} else {
return 0;
}
}
public static void main(String[] args) {
Main m = new Main();
System.out.println(m.temp());
System.out.println(m.same());
}
}
In this simplest of Java code, the temp() method issues no compiler error even though the return type of the function is int, and we are trying to return the value null (through the statement return true ? null : 0;). When compiled, this obviously causes the run time exception NullPointerException.
However, it appears that the same thing is wrong if we represent the ternary operator with an if statement (as in the same() method), which does issue a compile-time error! Why?
The compiler interprets null as a null reference to an Integer, applies the autoboxing/unboxing rules for the conditional operator (as described in the Java Language Specification, 15.25), and moves happily on. This will generate a NullPointerException at run time, which you can confirm by trying it.
I think, the Java compiler interprets true ? null : 0 as an Integer expression, which can be implicitly converted to int, possibly giving NullPointerException.
For the second case, the expression null is of the special null type see, so the code return null makes type mismatch.
Actually, its all explained in the Java Language Specification.
The type of a conditional expression is determined as follows:
If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.
Therefore the "null" in your (true ? null : 0) gets an int type and then is autoboxed to Integer.
Try something like this to verify this (true ? null : null) and you will get the compiler error.
In the case of the if statement, the null reference is not treated as an Integer reference because it is not participating in an expression that forces it to be interpreted as such. Therefore the error can be readily caught at compile-time because it is more clearly a type error.
As for the conditional operator, the Java Language Specification §15.25 “Conditional Operator ? :” answers this nicely in the rules for how type conversion is applied:
If the second and third operands have the same type (which may be the null
type), then that is the type of the conditional expression.
Does not apply because null is not int.
If one of the second and third operands is of type boolean and the type of the
other is of type Boolean, then the type of the conditional expression is boolean.
Does not apply because neither null nor int is boolean or Boolean.
If one of the second and third operands is of the null type and the type of the
other is a reference type, then the type of the conditional expression is that
reference type.
Does not apply because null is of the null type, but int is not a reference type.
Otherwise, if the second and third operands have types that are convertible
(§5.1.8) to numeric types, then there are several cases: […]
Applies: null is treated as convertible to a numeric type, and is defined in §5.1.8 “Unboxing Conversion” to throw a NullPointerException.
The first thing to keep in mind is that Java ternary operators have a "type", and that this is what the compiler will determine and consider no matter what the actual/real types of the second or third parameter are. Depending on several factors the ternary operator type is determined in different ways as illustrated in the Java Language Specification 15.26
In the question above we should consider the last case:
Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing conversion to S2. The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7).
This is by far the most complex case once you take a look at applying capture conversion (§5.1.10) and most of all at lub(T1, T2).
In plain English and after an extreme simplification we can describe the process as calculating the "Least Common Superclass" (yes, think of the LCM) of the second and third parameters. This will give us the ternary operator "type". Again, what I just said is an extreme simplification (consider classes that implement multiple common interfaces).
For example, if you try the following:
long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));
You'll notice that resulting type of the conditional expression is java.util.Date since it's the "Least Common Superclass" for the Timestamp/Time pair.
Since null can be autoboxed to anything, the "Least Common Superclass" is the Integer class and this will be the return type of the conditional expression (ternary operator) above. The return value will then be a null pointer of type Integer and that is what will be returned by the ternary operator.
At runtime, when the Java Virtual Machine unboxes the Integer a NullPointerException is thrown. This happens because the JVM attempts to invoke the function null.intValue(), where null is the result of autoboxing.
In my opinion (and since my opinion is not in the Java Language Specification many people will find it wrong anyway) the compiler does a poor job in evaluating the expression in your question. Given that you wrote true ? param1 : param2 the compiler should determine right away that the first parameter -null- will be returned and it should generate a compiler error. This is somewhat similar to when you write while(true){} etc... and the compiler complains about the code underneath the loop and flags it with Unreachable Statements.
Your second case is pretty straightforward and this answer is already too long... ;)
CORRECTION:
After another analysis I believe that I was wrong to say that a null value can be boxed/autoboxed to anything. Talking about the class Integer, explicit boxing consists in invoking the new Integer(...) constructor or maybe the Integer.valueOf(int i); (I found this version somewhere). The former would throw a NumberFormatException (and this does not happen) while the second would just not make sense since an int cannot be null...
Actually, in the first case the expression can be evaluated, since the compiler knows, that it must be evaluated as an Integer, however in the second case the type of the return value (null) can not be determined, so it can not be compiled. If you cast it to Integer, the code will compile.
private int temp() {
if (true) {
Integer x = null;
return x;// since that is fine because of unboxing then the returned value could be null
//in other words I can say x could be null or new Integer(intValue) or a intValue
}
return (true ? null : 0); //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
//value can be Integer
// then null is accepted to be a variable (-refrence variable-) of Integer
}
How about this:
public class ConditionalExpressionType {
public static void main(String[] args) {
String s = "";
s += (true ? 1 : "") instanceof Integer;
System.out.println(s);
String t = "";
t += (!true ? 1 : "") instanceof String;
System.out.println(t);
}
}
The output is true, true.
Eclipse color codes the 1 in the conditional expression as autoboxed.
My guess is the compiler is seeing the return type of the expression as Object.