7

I found a strange behaviour in the current version of Java 8. In my opinion the following code should be fine, but the JVM throws a NullPointerException:

Supplier<Object> s = () -> false ? false : false ? false : null;
s.get(); // expected: null, actual: NullPointerException

It doesn't matter, what kind of lambda expression it is (same with java.util.function.Function) or what generic types are used. There can also be more meaningful expressions in place of false ? :. The example above is very short. Here's a more colorful example:

Function<String, Boolean> f = s -> s.equals("0") ? false : s.equals("1") ? true : null;
f.apply("0"); // false
f.apply("1"); // true
f.apply("2"); // expected: null, actual: NullPointerException

However these code pieces run fine:

Supplier<Object> s = () -> null;
s.get(); // null

and

Supplier<Object> s = () -> false ? false : null;
s.get(); // null

Or with function:

Function<String, Boolean> f = s -> {
    if (s.equals("0")) return false;
    else if (s.equals("1")) return true;
    else return null;
};
f.apply("0"); // false
f.apply("1"); // true
f.apply("2"); // null

I tested with two Java versions:

~# java -version

openjdk version "1.8.0_66-internal" OpenJDK Runtime Environment (build 1.8.0_66-internal-b01) OpenJDK 64-Bit Server VM (build 25.66-b01, mixed mode)

C:\>java -version

java version "1.8.0_51" Java(TM) SE Runtime Environment (build 1.8.0_51-b16) Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)

steffen
  • 16,138
  • 4
  • 42
  • 81
  • 4
    How is this different from `Object a = false ? false : false ? false : null;` which also yields a `NullPointerException`? – serv-inc Aug 12 '15 at 16:15
  • It occurs in Java 7 and Java 8 (OpenJDK). – serv-inc Aug 12 '15 at 16:23
  • 1
    @bayou.io How so? Lambdas don't add anything. The "problem" is ternary expressions. – Sotirios Delimanolis Aug 12 '15 at 16:35
  • I think the answer to this question is the unboxing of `null` to a `boolean` value because of the binding/precedence in the use of the two `?:` operators. The duplicate post says as much (maybe not so much about the binding, but implies it). There's no need for 10 different posts about this. If you have a better duplicate, I'd be happy to reopen and we can reclose it as such. This question has been answered many times over. – Sotirios Delimanolis Aug 12 '15 at 16:41
  • 1
    @steffen - apparently there is unboxing somewhere. but it's unclear why; or whether it's legal or a bug. I bet even the language designer couldn't answer it off the top of their head. java8 really makes `?:` even more complicated. See my analysis [here](https://groups.google.com/forum/#!topic/java-lang-fans/-ap9v5MDO_4) – ZhongYu Aug 12 '15 at 16:49

1 Answers1

9

This has nothing to do with lambda expressions; it is simply that the return type of the ternary operator in that case is boolean, so auto-unboxing will be used.

NPE is thrown here as well:

public class Main {

    private static Object method() {
        return false ? false : false ? false : null;
    }

    public static void main(String[] args) {
        System.out.println(method());
    }
}

So, what exactly is happening here?

Firstly, 'embedded' expression (false ? false : null) is evaluated according to JLS §15.25:

The conditional operator is syntactically right-associative (it groups right-to-left). Thus, a?b:c?d:e?f:g means the same as a?b:(c?d:(e?f:g)).

The type of the embedded expression is Boolean (boxed boolean), so that both false and null can fit into it.

Then the whole expression is:

false ? false : (Boolean expression)

Then, again according to JLS §15.25:

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.

So, the first argument is of primitive type boolean (T in the spec), the type of the other is the boxed T (Boolean), so the type of the entire expression is boolean.

Then, in runtime, the embedded expression evaluates to null which is auto-unboxed to boolean, causing the NPE.

Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
  • 1
    well, OP's code is in java8, so we must consider target typing `Object`, and we must analyze whether it should affect operands; why unboxing is done instead of boxing. – ZhongYu Aug 12 '15 at 17:17
  • Then why does `false ? false : false ? Boolean.FALSE : null;` still throw an NPE. The second operand of the nested expression is `Boolean`, so no boxing conversion will be applied (§5.1.7). The outer expression would be `false ? false : (Boolean expression)`. The next line in JLS §15.25 reads `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.` OK, but finally that would be equal to `false ? false : (Boolean) null`. – steffen Aug 12 '15 at 18:24
  • 1
    `false ? false : false ? Boolean.FALSE : null` is the same thing. The embedded ternary expression evaluates to `Boolean`, and the entire expression returns `boolean`, because it's the primitive type `T` quoted above. – Dragan Bozanovic Aug 12 '15 at 19:44
  • 1
    Regarding the paragraph you quoted, there is no such an occurence in the expression, `false` is not reference type, and `(Boolean) null` is not null type. – Dragan Bozanovic Aug 12 '15 at 20:29
  • The quoted JLS sentence applies to `false ? false : false ? Boolean.FALSE : null` (one of the 2,3 operarands is `null`, the other is reference type). Hence the type of the inner expression is that reference type (`Boolean`, no autoboxing). The whole expression therefore is `boolean ? boolean : Boolean`, which gives an NPE. But in regards to the original question, your explanation is perfect (+1), at least while bayou.io doesn't find Java 8 specifics. – steffen Aug 13 '15 at 08:39