62

I know that when I read the answer to this I will see that I have overlooked something that was under my eyes. But I have spent the last 30 minutes trying to figure it out myself with no result.

So, I was writing a program in Java 6 and discovered some (for me) strange feature. In order to try and isolate it, I have made two small examples. I first tried the following method:

private static int foo()
{
    return null;
}

and the compiler refused it: Type mismatch: cannot convert from null to int.

This is fine with me and it respects the Java semantics I am familiar with. Then I tried the following:

private static Integer foo(int x)
{
    if (x < 0)
    {
        return null;
    }
    else
    {
        return new Integer(x);
    }
}

private static int bar(int x)
{
    Integer y = foo(x);

    return y == null ? null : y.intValue();
}

private static void runTest()
{
    for (int index = 2; index > -2; index--)
    {
        System.out.println("bar(" + index + ") = " + bar(index));
    }
}

This compiles with no errors! But, in my opinion, there should be a type conversion error in the line

    return y == null ? null : y.intValue();

If I run the program I get the following output:

bar(2) = 2
bar(1) = 1
bar(0) = 0
Exception in thread "main" java.lang.NullPointerException
    at Test.bar(Test.java:23)
    at Test.runTest(Test.java:30)
    at Test.main(Test.java:36)

Can you explain this behaviour?

Update

Thank you very much for the many clarifying answers. I was a bit worried because this example did not correspond to my intuition. One thing that disturbed me was that a null was being converted to an int and I was wondering what the result would be: 0 like in C++? That would hae been very strange. Good that the conversion is not possible at runtime (null pointer exception).

Giorgio
  • 5,023
  • 6
  • 41
  • 71

6 Answers6

67

Let's look at the line:

return y == null ? null : y.intValue();

In a ? : statement, both sides of the : must have the same type. In this case, Java is going to make it have the type Integer. An Integer can be null, so the left side is ok. The expression y.intValue() is of type int, but Java is going to auto-box this to Integer (note, you could just as well have written y which would have saved you this autobox).

Now, the result has to be unboxed again to int, because the return type of the method is int. If you unbox an Integer that is null, you get a NullPointerException.

Note: Paragraph 15.25 of the Java Language Specification explains the exact rules for type conversions with regard to the ? : conditional operator.

Jesper
  • 202,709
  • 46
  • 318
  • 350
  • What is the general rule for how the compiler attempts to resolve types in a ternary expression? – Oliver Charlesworth Jul 05 '11 at 21:22
  • 2
    +1, this makes sense now, for me. Why does the compiler not allow the first test, `int foo() { return null; }`, as in this case again, it should autobox `null` to `Integer` ? or, I guess my question in other words is, why does it chose to autobox the fields in the ternary operation to `Integer` and doesn't leave them as primitives ? Where is that decision based upon ? – c00kiemon5ter Jul 05 '11 at 21:22
  • @Oli I'm not sure but it can have surprising bahaviour, of which this is an example. See [paragraph 15.25](http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.25) of the JLS. – Jesper Jul 05 '11 at 21:26
  • @c00kiemonster I guess because it's too obvious that `null` is not an `int`, the compiler is not going to box and then directly unbox the `null`. You have the problem because of the `? :` which sometimes behaves counter-intuitively. – Jesper Jul 05 '11 at 21:28
20

Guava has a pretty elegant solution for this using MoreObjects.firstNonNull:

Integer someNullInt = null;
int myInt = MoreObjects.firstNonNull(someNullInt, 0);
michaelgulak
  • 631
  • 1
  • 6
  • 14
8

Just in case you don't have Guava in your project, but already using Apache Commons, you might utilize Apache Lang3 with its ObjectUtils class.

The usage is basically the same as Guava:

Integer number = null;
int notNull = ObjectUtils.firstNonNull(number, 0);

Note, that this method in Guava library works faster, than in Apache. Here is a short comparison I just made on my laptop (Core i7-7500U 2.7 GHz), Oracle Java 8, multiple runs, JVM preheated, results are averaged:

╔══════════════╦══════╦══════╦════════╦══════╗
║ Library/Runs ║ 1000 ║ 1mln ║ 100mln ║ 1bln ║
╠══════════════╬══════╬══════╬════════╬══════╣
║ Apache       ║    1 ║   30 ║    782 ║ 9981 ║
║ Guava        ║    1 ║   22 ║    120 ║  828 ║
╚══════════════╩══════╩══════╩════════╩══════╝

Results are in milliseconds. I don't think you often need to run this method for billions of times, but still, it is always good to have performance comparison

Hasan Ammori
  • 373
  • 3
  • 9
6

The type of the return type is inferred by Java here. That is the issue ..

http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.25

Here is the actual problem --

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, basically the compiler infers the return type of the conditional expression to be Integer and thats why it allows you to compile successfully.

EDIT : See rules in comments

Kal
  • 24,724
  • 7
  • 65
  • 65
  • 1
    @Kai: Now I start to understand: the compiler determines the result type of the ? operator first and finds Integer. Only then, it tries to match Integer with the return type int. They are compatible provided that an unboxing is performed. It never sees that null cannot be unboxed to int. In the first example there is only one conversion step, that's why the compiler says the types are incompatible. – Giorgio Jul 05 '11 at 21:28
  • @Giorgio -- yes. If you look at the link, there is further clarification --- It boxes both operands and then applies the same logic ( in this case, int gets boxed to Integer ) .. return type is assumed Integer. **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). – Kal Jul 05 '11 at 21:51
  • 1
    "*and the type of the other is a reference type*" <--- an int is NOT a reference, so does not meet this paragraph. – Mr.Eddart Jul 06 '11 at 11:23
3

This illustrates a problematic difference between the way a human reads code and a compiler reads code.

When you see a ternary expression, it's very possible for you to mentally split it into two parts, in the style of an if/else statement:

if (y == null)
    return null;
else
    return y.intValue();

You can see that this is invalid, as it results in a possible branch where a method defined to return an int is actually returning null (illegal!).

What the compiler sees is an expression, which must have a type. It notes that the ternary operation includes a null on one side and an int on the other; due to Java's autoboxing behavior, it then comes up with a "best guess" (my term, not Java's) as to what the expression's type is: Integer (this is fair: it's the only type which could legally be null or a boxed int).

Since the method is supposed to return an int, this is fine from the compiler's perspective: the expression being returned evaluates to an Integer, which can be unboxed automatically.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Dan Tao
  • 125,917
  • 54
  • 300
  • 447
1

The problem with autounboxing null values can be really annoying. In your example it's a combination of ternary operator result type inferring and autounboxing (the JLS should be consulted why it behaves like that)

But generally, you should try to avoid using wrapper types. Use int instead of Integer. If you need a special value meaning "no result", then you can use Integer.MAX_VALUE for example.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • That's why I have a return type int. The null comes from a copy and paste and when I saw that the compiler did not complain I was quite surprised. – Giorgio Jul 05 '11 at 21:20