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.
Related
Following instanceof operator is working fine, when I type cast string to Object.
However, Without typecasting it is failing to compile.
public class Test {
public static void main(String[] args) {
boolean b1 = "abc" instanceof Integer; // Compilation fails
boolean b2 = ((Object) "abc") instanceof Integer; // Works fine
}
}
My question is why compiler is rejecting the first one b1 but allowing the second one b2
Because you've casted "abc" to Object, the compiler treats ((Object) "abc") the same as any other expression of Object type.
As such, it thinks that it could contain any Object (or null). The compiler doesn't deeply inspect the expression to determine a more specific type.
You can also write:
Object obj = "abc";
boolean b2 = obj instanceof Integer;
It's basically the same.
It stops you writing "abc" instanceof Integer because both String and Integer are classes, and they are unrelated, so a String can never be an instance of an Integer. (It would allow "abc" instanceof List, because List is an interface, and it doesn't consider the final-ness of a class, and assumes there might be a subclass of String that could implement List).
In a sense, casting is you telling the compiler that you know more than it does. Although the compiler is able to push back in very basic cases where it can determine that you are doing something nonsensical, it largely gets out of the way and trusts you (in a "on your head be it" kind of way).
JLS 15.20 and 15.20.2 state this:
RelationalExpression:
...
RelationalExpression instanceof ReferenceType
If a cast of the RelationalExpression to the ReferenceType would be rejected as a compile-time error (§15.16), then the instanceof relational expression likewise produces a compile-time error. In such a situation, the result of the instanceof expression could never be true.
In your first example expression ("abc" instanceof Integer) "abc" (a String) cannot be cast to Integer. Therefore the instanceof expression is a compilation error.
In your second example expression (((Object) "abc") instanceof Integer), the left operand has type Object. In some cases, an Object may be an Integer. Therefore the left-hand operand can be cast to the right-hand type. Therefore the instanceof is allowed.
It is worth noting that the JLS is only concerning itself with the compile-time types of the expressions. In the second example, we can easily deduce that the run-time type of the left hand operand is always String and the expression will always evaluate to false. But the JLS rules don't classify this "mistake" as a compilation error.
Instanceof can only be used between parent and child classes
the first one "abc" is String type not Integer.
but the second one ((Object) "abc") is Object type, Integer is a child of Object
I'm upgrading my project to Java 1.7 to 1.8. When I compile my code, it gives a compile error as int cannot cast to long.
Here is the expression which returns a primitive long.
public Long getOrganizationIdByUser(String userId){
Query query = getSession().getNamedQuery("getOrganizationIdByUser");
query.setString("id", userId!=null?_userId.toLowerCase():null);
List<Long> returnList = query.list();
return returnList!=null&&returnList.size()>0?returnList.get(0):0;
}
I've confused why 0 cannot cast to long in java 1.8 since this is a basic concept in java and also it works fine in java 1.7.
Error
error: incompatible types: bad type in conditional expression
[javac] return returnList!=null&&returnList.size()>0?returnList.get(0):0;
[javac]
[javac] int cannot be converted to Long
Numeric conditional expressions are subject to “Binary Numeric Promotion”, which is the reason why the following code can be compiled:
Long long1 = null;
Integer int1 = null;
Long long2 = true? long1: int1;
Since both alternatives have a numeric type, the numeric promotion applies, which will first unbox the values, then apply the widening conversion to the int operand, so the conditional expression has a result type of long. Only after, the boxing to Long happens.
So the code above has the unintuitive behavior of throwing a NullPointerException instead of directly assigning the object found in long1 to long2, due to the unboxing and boxing steps.
The same applies to the combination of Long and int, also when the result of the one operand is a method invocation, e.g.
static Long test() {
return Math.random()<0.5? test(): 0;
}
can be compiled without problems. So what’s the difference? In the example
public Long test() {
List<Long> returnList = new ArrayList<Long>();
return returnList!=null&&!returnList.isEmpty()? returnList.get(0): 0;
}
the method invocation is made on an instance of a generic class.
The specification says:
…
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:
An expression of a standalone form (§15.2) with a type that is convertible to a numeric type (§4.2, §5.1.8).
A parenthesized numeric expression (§15.8.5).
A class instance creation expression (§15.9) for a class that is convertible to a numeric type.
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.
A numeric conditional expression
Note that this has been changed compared to Java 7, due to the introduction of the “standalone form” expression, which is the opposite of the “poly expression” which is subject to target typing.
To check whether the method invocation is a stand-alone expression or a poly expression, we have to refer to JLS §15.12:
A method invocation expression is a poly expression if all of the following are true:
The invocation appears in an assignment context or an invocation context (§5.2, §5.3).
If the invocation is qualified (that is, any form of MethodInvocation except for the first), then the invocation elides TypeArguments to the left of the Identifier.
The method to be invoked, as determined by the following subsections, is generic (§8.4.4) and has a return type that mentions at least one of the method's type parameters.
Otherwise, the method invocation expression is a standalone expression.
In our case, the last bullet does not apply. This method is a member of a generic class, but it is itself not generic as it doesn’t declare type parameters, so it’s return type doesn’t refer to a method’s type parameter, as there is none.
In other words, this method invocation is a stand-alone expression, having a numeric return type, so the conditional expression is a numeric conditional expression, and should be subject to Binary Numeric Promotion, just like the other examples.
Note that all recent Java 9 implementation do compile this code, just like the Java 6 and Java 7 implementations do.
Try adding 'L' at the end :
returnList != null && returnList.size()>0 ? returnList.get(0) : 0L;
Or if 'returnList.get(0)' return an int, try:
returnList != null && returnList.size()>0 ? (long)returnList.get(0) : 0L;
This question already has an answer here:
why I can set primitive types to null in ternary operations
(1 answer)
Closed 8 years ago.
In Java when I write like this
public int method(boolean b) {
if (b)
return null;
else
return 0;
}
the compiler complains incompatible types, but if substitute this with shorthand
public int method(boolean b) {
return (b ? null : 0);
}
the compiler doesn't complain, moreover there will be a NPE.
So my questions are
Why compiler doesn't complain
Why NPE?
This is caused by a combination of auto-unboxing, and type inference.
In both cases, it is clear from the type signature that the method must return an int.
In your first case, you are explicitly returning null, which is not assignable to int, so the compiler rightly complains that this is an error.
In the second case, you're creating an anonymous value (the bit in the brackets), and so the compiler must infer the type of it. It works out that the most specific common supertype of 0 and null is Integer - which is correct. Therefore, your return statement is returning something of type Integer - and this is compatible with int, it just gets auto-unboxed at runtime. It is this auto-unboxing that throws the NPE, when the VM attempts to convert a null reference into an int.
If it helps you to visualise it better, your second example is essentially the same as:
public int method(boolean b) {
Integer tmp = (b ? null : 0);
return tmp;
}
and so the compiler has nothing to complain about (both lines are fine on their own).
The fault here, if there is one, is with auto-unboxing, and silently pretending that Integer is the same type as int. It isn't, for exactly this reason.
The problem is the type that you want to return.
You can't assign null to a primitive type.
It's an half answer I don't know why it doesn't complain in the shorthand version.
Why is the compiler able to determine the generic type parameter for an
assignment, but not for the ternary operator (?)?
I have a question regarding the compiler being able to deduce the generic type
parameter in case of a "direct" assignment but failing in case of the ternary
operator (?). My examples uses Guava's Optional class, to make my point, but
I think the underlying issue is generic and not restricted to Optional.
Optional has a generic function absent():
public static <T> Optional<T> absent();
and I can assign an Optional<T> to an Optional<Double>:
// no compiler error
final Optional<Double> o1 = Optional.absent();
How does the compiler realize, that T should be Double in this case. Because
when using the ternary operator (?), I need to tell the compiler specifically
to us Integer as the generic parameter
// Type mismatch: cannot convert from Optional<capture#1-of ? extends Object> to Optional<Integer>
final Optional<Integer> o2 = true
? Optional.of(42)
: Optional.<Integer>absent();
otherwise I get the following error
Type mismatch: cannot convert from Optional<capture#1-of ? extends Object> to Optional<Integer>
Why is there a difference between a "direct" assignement and using the ternary
operator? Or is there something else I am missing?
Because of type inference rules, it appears the ternary expression does not infer the type parameter from the return type. The type of the ternary expression depends on the types of its operands. But one of the operands has undetermined type parameter (Optional.absent()). At that point the ternary expression still does not have a type, so it cannot influence the type parameter.
You can also look into this bug report for more information. You can look into the JLS .
The type of the conditional expression is the result of applying capture conversion (??5.1.10) to lub(T1, T2)
Here what the JLS says :
If the method result occurs in a context where it will be subject to assignment conversion to a type S, then let R be the declared result type of the method, and let R' = R[T1 = B(T1) ... Tn = B(Tn)] where B(Ti) is the type inferred for Ti in the previous section, or Ti if no type was inferred.
The Problem is that the result of the ternery Operator is assigned to o2.
The compiler can't deduce the type across multiple operations.
Basically I think you write the short form of:
Optional<?> tmp = true ? Optional.of(42): Optional.absent();
final Optional<Integer> o2 = tmp;
The conversion of the second line is the problem.
Given the generic method:
<T> List<T> getGenericList(int i) {...}
the following code compiles without any warning:
public List<String> getStringList(boolean b){
if(b)
return getGenericList(0);
else
return getGenericList(1);
}
but this one generates 'Type mismatch' compilation error:
public List<String> getStringList(boolean b) {
return (b) ? getGenericList(0) : getGenericList(1);
}
Why?
This is NOT a generics problem, but a consequence of the way the compiler has to infer the type of the ternary expression.
It happens the same with this equivalent code. This code works:
public byte function(boolean b){
if(b)
return 1;
else
return 2;
}
While this doesn't:
public byte function(boolean b) {
return (b) ? 1 : 2;
}
The reason is that when the compiler tries infer the type of this expression
return (b) ? 1 : 2;
It first has to obtain the type of each one of the operands, and check if they are compatible (reference) to evaluate wether the ternary expression is valid or not. If the type of the "return" were propagated to automatically cast or promote each one of the operands, it could lead to resolve the type of a ternary expression differently depending on the context of this expression.
Given that the type of the "return" cannot be propagated to the operands, then in the menctioned case:
return (b) ? getGenericList(0) : getGenericList(1);
the binding of the generic type cannot be done, so the type of each one of the operands is resolved to List<Object>. Then the compiler concludes that the type of the whole expression is List<Object>, which cannot be automatically casted to List<Integer> (because they are not compatible types).
Whereas this other one
return getGenericList(0);
It applyes the type of the "return" to bind the generic type T, so the compiler concludes that the expression has a List<String> type, that can be returned safely.
this is because of an edge case in generic type deduction
in the explicit returns the return type of each getGenericList can be trivially set to List (outward info propagates inwards)
but in the conditional it goes the other way the type of it is the more general of the two possibilities (inward info propagates outwards)
the compiler could deduct the info implicitly here but it's not buildin yet file a bug report if you really need it
When the trinary operator is evaluated, it's result is not bound to any type. It's as if you just call:
getGenericList(0);
Try compiling the above, the compilation will fail.
In return statement, the result is bound to your function's return type and is evaluated.
Edit:
I've mistaken. The above statement compiles, but the result type is evaluated as (List < Object > ). Try compiling:
List<String> l = (List<String>)getGenericList(0);
This one will fail.
This is because javac needs to infer T, but T does not appear in argument types.
static<T> T foo(){ .. }
foo(); // T=?
Only in 2 cases, javac can infer T from the context
String s = foo(); // #1: assignment
String bar(){
return foo(); // #2: return
In other cases, javac won't, and T is simply inferred as Object
But this type of methods are dangerous anyway. How could your method know it's time to return List<String>, not a list of something else? There's no info about T available to you.
It looks likes its just inefficient compilation.
As Maurice said, generally, the compiler determines the type of T by function arguments. In this case there aren't any.
However, because getStringList() returns List<String> and it calls return getGenericList(), the compiler is smart enough to forward the return type of getStringList to getGenericList and determine the type in that manner.
My guess is that in the ternary operator, it's doesn't bind in reverse, but rather finds the common denominator in each substatement and assigns that as the output of the ternary statement, and the compiler isn't smart enough to pass the expected output of the ternary statement into its substatements.
Note, you can directly pass the type parameter into the function call, and it works fine, ie:
return b ? this.<String> getGenericList(0) : this.<String> getGenericList(1);
compiles properly.