190

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?

Raedwald
  • 46,613
  • 43
  • 151
  • 237
Lion
  • 18,729
  • 22
  • 80
  • 110
  • 6
    Also, `int foo = (true ? null : 0)` and `new Integer(null)` both compile fine, the second being the explicit form of autoboxing. – Izkata Nov 11 '11 at 20:53
  • 2
    @Izkata the problem here is for me to understand why the compiler is trying to autobox `null` to `Integer`... That would look just like "guessing" to me or "making things work"... – Marsellus Wallace Nov 11 '11 at 21:23
  • 1
    ...Huhm, I thought I had an answer there, as the Integer constructor (what the docs I found say is used for autoboxing) is allowed to take a String as an argument (which can be null). However, they also say that the constructor acts identically to the method parseInt(), which would throw a NumberFormatException upon getting passed a null... – Izkata Nov 11 '11 at 22:35
  • 3
    @Izkata - the String argument c'tor for Integer is not an autoboxing oepration. A String can't be autoboxed to an Integer. (The function `Integer foo() { return "1"; }` won't compile.) – Ted Hopp Nov 12 '11 at 23:16
  • 5
    Cool, learned something new about the ternary operator! – oksayt Nov 17 '11 at 06:05
  • Also worth noting: `return (Integer)null;` compiles fine – Kip Nov 17 '11 at 21:24
  • \o\ Yay to the Runtime NullPointerExceptions /o/ Intriguing question tho, I wonder if will these things be ever changed. like `public void foo() { try { foo(); } finally { foo(); } }` being a nearly endless recursive function. – Felype Jun 07 '13 at 11:31

8 Answers8

120

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.

Nandkumar Tekale
  • 16,024
  • 8
  • 58
  • 85
Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • Given the link to the Java Language Specification that you posted, which point do you think gets executed in the case of the question above? The last one (since I'm still trying to understand `capture conversion` and `lub(T1,T2)`)?? Also, Is it really possible to apply boxing to a null value? Wouldn't this be like "guessing"?? – Marsellus Wallace Nov 11 '11 at 21:03
  • ´@Gevorg A null pointer is a valid pointer to every possible object so nothing bad can happen there. The compiler just assumes that null is an Integer which it then can autobox to int. – Voo Nov 12 '11 at 19:42
  • 1
    @Gevorg - See nowaq's comment and my response to his post. I think he picked the correct clause. `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.) – Ted Hopp Nov 12 '11 at 23:13
  • @TedHopp I analyzed `lub(T1,T2)` a lot (see my comments all around and my answer below). I thought the answer was in there as well but I changed my mind since I do not believe that `null` can be boxed to anything. Explicit boxing of `null` to `Integer` -`new Integer(null);` would throw a `NumberFormatException` and this does not happen. – Marsellus Wallace Nov 15 '11 at 05:18
  • 8
    @Gevorg - `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. – Ted Hopp Nov 15 '11 at 14:30
  • @TedHopp I see you're point of view but looking at the JLS that you posted: `Let T1 be the type that results from applying boxing conversion to S1` still makes me think that the JVM applies boxing to `null`. Maybe this is not the case to look at though.. – Marsellus Wallace Nov 18 '11 at 18:20
  • 1
    @Gevorg - If you look at the [rules for boxing conversion](http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#190697), and apply them to `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 of `null` to `Integer` yields `null`, without invoking any `Integer` constructor. – Ted Hopp Nov 18 '11 at 18:50
  • It seems to me that what is actually happening here is that the compiler only goes as far as the boxing conversion of both, to get a null Int and a zero Int. But unboxing a null throws a null-pointer exception, so **when it tries to unbox the null Int at run time**, it gets that error. This is probably what you are saying. Saying "autoboxing/unboxing" makes it sound like both happened at compile here, but I think you were just referring to the rules in general which include both things, but only boxing happens for the code above. –  Jul 13 '15 at 19:29
  • @Anon316 - Actually, both the boxing and unboxing conversions happen at run time. For the ternary operator, the compiler either pushes `null` to the stack directly or else pushes 0 and then calls the static method `Integer.valueOf()` (which replaces the top element on the stack with a reference to an `Integer` object). It then generates bytecode to obtain the return value via an instance method call to `Integer.intValue()` using the presumed `Integer` object reference on the stack. The NPE is triggered when the runtime discovers that the object reference is `null`. – Ted Hopp Jul 13 '15 at 21:20
  • @TedHopp - yes, both boxing and unboxing happen at runtime, but what happens at compile time. I read the specs to say that, for this case, only boxing happens - boxing the 0 to Int, and using that to determine the result between null and Int (a reference type) is the reference type Int. –  Jul 13 '15 at 21:40
  • @Anon316 - Ah, now I understand our discussion. The spec is a bit confusing because it is talking about boxing conversion of _types_, not of values. So when the compiler does a type analysis, it applies a boxing conversion to the null type (not to the value `null`) and to the primitive type `int` (not to the value `0`). The results are the null type (the null type is its own boxing conversion) and the `Integer` reference type. Then the least upper bound of those two types is `Integer`. – Ted Hopp Jul 13 '15 at 22:11
40

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.

Tiny
  • 27,221
  • 105
  • 339
  • 599
Vlad
  • 35,022
  • 6
  • 77
  • 199
  • 2
    I assume this is related to auto-boxing? Presumably the first return would *not* compile prior to Java 5, right? – Michael McGowan Nov 11 '11 at 19:37
  • @Michael that appears to be the case if you set Eclipse's compliance level to pre-5. – Jonathon Faust Nov 11 '11 at 19:42
  • @Michael: this definitely looks like auto-boxing (I'm quite new to Java, and cannot make more defined statement -- sorry). – Vlad Nov 11 '11 at 19:42
  • 1
    @Vlad how would the compiler end up interpreting `true ? null : 0` as `Integer`? By autoboxing `0` first?? – Marsellus Wallace Nov 11 '11 at 20:53
  • 1
    @Gevorg: Look [here](http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.25): _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._ and the following text. – Vlad Nov 11 '11 at 20:55
  • @Vlad I'm still trying to understand `capture conversion` and `lub(T1,T2)` and I don't understand how the compiler could try to autobox a null value to anything... Wouldn't that be random guessing to "make things work"? The alternative would be autoboxing 0 to Integer first, then we would fall into point 3 of your link, but I'm not sure that this is what's happening here.. – Marsellus Wallace Nov 11 '11 at 21:10
  • @Gevorg: I think it's autoboxing int `0` to Integer. – Vlad Nov 11 '11 at 21:13
  • @Vlad right, that would be the last case of the language specification and my best guess as well. But it's hard for me to believe that the compiler would then jump to point 3 since I believe that it should check the different possibilities in order as suggested by those "if, otherwise"... `lub(T1,T2)` is pretty painful to understand for me.. – Marsellus Wallace Nov 11 '11 at 21:32
  • @Vlad also, if you try to explicitly box `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. – Marsellus Wallace Nov 15 '11 at 05:20
  • @Gevorg: well, I would use `(Integer)null` for explicating the intention: `new Integer(null)` calls the constructor [`Integer(String)`](http://download.oracle.com/javase/6/docs/api/java/lang/Integer.html#Integer%28java.lang.String%29). – Vlad Nov 15 '11 at 08:56
  • @Vlad that's casting and not explicit boxing. Explicit boxing is defined as using the constructor or the valueOf function – Marsellus Wallace Nov 18 '11 at 18:21
32

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.

Tiny
  • 27,221
  • 105
  • 339
  • 599
nowaq
  • 2,748
  • 19
  • 25
  • 3
    But that clause of the rules doesn't apply: the second and third operands do _not_ have the same type. – Ted Hopp Nov 11 '11 at 20:25
  • 1
    Then the answer seems to be in the following statement: > 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). – nowaq Nov 11 '11 at 20:50
  • I think that's the applicable clause. Then it tries to apply auto-unboxing in order to return an `int` value from the function, which causes a NPE. – Ted Hopp Nov 12 '11 at 23:11
  • @nowaq I thought this too. However, if you try to explicitly box `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... – Marsellus Wallace Nov 15 '11 at 05:21
  • @Gevorg I'd think since an exception happens when doing the boxing we don't get ANY result here. The compiler is just obliged to generate code that follows the definition which it does - we just get the exception before we're done. – Voo Nov 15 '11 at 20:15
  • @Voo - There is no exception generated when boxing anything. The exception happens when _un_boxing the null in order to return an int value from the method. If the method signature were changed to return an Integer, there would be no exception at all -- it would just return `null`. – Ted Hopp Nov 15 '11 at 22:33
25

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.
Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
  • If `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)... – Marsellus Wallace Nov 11 '11 at 21:36
  • @Gevorg - Why is it hard to believe that the ternary operator is returning an `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. – Ted Hopp Nov 12 '11 at 23:20
  • 2
    @TedHopp: Gevorg was responding to an earlier revision of my answer, which was incorrect. You should ignore the discrepancy. – Jon Purdy Nov 13 '11 at 07:56
  • @JonPurdy "A type is said to be convertible to a numeric type if it is a numeric type, or it is a reference type that may be converted to a numeric type by unboxing conversion" and I don't think that `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.. – Marsellus Wallace Nov 18 '11 at 18:28
  • @Gevorg: Unboxing happens at runtime. The `null` is treated as though it had type `int`, but is actually equivalent to `throw new NullPointerException()`, that’s all. – Jon Purdy Nov 18 '11 at 20:34
11

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

Tiny
  • 27,221
  • 105
  • 339
  • 599
Marsellus Wallace
  • 17,991
  • 25
  • 90
  • 154
  • 1
    The `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.) – Ted Hopp Nov 15 '11 at 22:37
4

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.

Lion
  • 18,729
  • 22
  • 80
  • 110
GeT
  • 71
  • 2
2
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
}
Youans
  • 4,801
  • 1
  • 31
  • 57
0

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.

Jon
  • 555
  • 5
  • 10