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?
int foo = (true ? null : 0)
and new Integer(null)
both compile fine, the second being the explicit form of autoboxing.
null
to Integer
... That would look just like "guessing" to me or "making things work"...
Integer foo() { return "1"; }
won't compile.)
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.
true ? null : 0
as Integer
? By autoboxing 0
first??
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.
int
value from the function, which causes a NPE.
null
to Integer
with new Integer(null);
"Let T1 be the type that results from applying boxing conversion to S1..." you would get a NumberFormatException
and this is not the case...
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.
0
is autoboxed to Integer
then the compiler is executing the last case of the "ternary operator rules" as described in the Java Language Specification. If that's true, then it's hard for me to believe that it would then jump to case 3 of the same rules that is having a null and a reference type that make the return value of the ternary operator being the reference type (Integer)...
Integer
? That's exactly what's happening; the NPE is being generated by trying to unbox the expression value in order to return an int
from the function. Change the function to return an Integer
and it will return null
with no problem.
null
falls in this category. Also, we would then go into the "Otherwise, binary numeric promotion (§5.6.2) is applied ... Note that binary numeric promotion performs unboxing conversion (§5.1.8) ..." step to determine the return type. But unboxing conversion would generate a NPE and this happens only at runtime and not while trying to determine the ternary operator type. I'm still confused..
null
is treated as though it had type int
, but is actually equivalent to throw new NullPointerException()
, that’s all.
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
...
null
in OP's original code is not boxed. The way it works is: the compiler assumes that the null
is a reference to an Integer. Using the rules for ternary expression types, it decides the entire expression is an Integer expression. It then generates code to autobox the 1
(in case the condition evaluates to false
). During execution, the condition evaluates to true
so the expression evaluates to null
. When trying to return an int
from the function, the null
is unboxed. That then throws a NPE. (The compiler might optimize most of this away.)
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.
Success story sharing
capture conversion
andlub(T1,T2)
)?? Also, Is it really possible to apply boxing to a null value? Wouldn't this be like "guessing"??lub(T1,T2)
is the most specific reference type in common in the type hierarchy of T1 and T2. (They both share at least Object, so there is always a most specific reference type.)null
is not boxed into an Integer, it is interpreted as a reference to an Integer (a null reference, but that's not a problem). No Integer object is constructed from the null, so there's no reason for a NumberFormatException.null
(which is not a primitive numeric type), the applicable clause is "If p is a value of any other type, boxing conversion is equivalent to an identity conversion". So boxing conversion ofnull
toInteger
yieldsnull
, without invoking anyInteger
constructor.