Почему тройная операция дает nullpointer, в то время как ее ifelse-аналог не делает?

Я получаю NullPointerException в одном экземпляре ниже, в то время как его копия работает плавно.

public static void main(String[] args){
    System.out.println(withTernary(null, null)); //Null Pointer
    System.out.println(withIfElse(null, null));  //No Exception
}

private static Boolean withTernary(String val, Boolean defVal){
    return val == null ? defVal : "true".equalsIgnoreCase(val);
}

private static Boolean withIfElse(String val, Boolean defVal){
    if (val == null) return defVal;
    else return "true".equalsIgnoreCase(val);
}

Онлайн-версия

Онлайн-версия с линиями в main в обратном порядке, которая выводит null из withIfElse, а затем выходит из строя withTernary.

Я использую следующую версию java

java version "1.6.0_65"
Java(TM) SE Runtime Environment (build 1.6.0_65-b14-462-11M4609)
Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-462, mixed mode)

Ответы

Ответ 1

Здесь соответствующая цитата из спецификация (§15.25.2):

Булевы условные выражения являются автономными выражениями (§15.2).

Тип булевого условного выражения определяется следующим образом:

  • Если второй и третий операнды имеют тип Boolean, условное выражение имеет тип Boolean.

  • В противном случае условное выражение имеет тип Boolean.

Следовательно, общий тип выражения считается Boolean, а значение Boolean является autounboxed, вызывая NullPointerException.


Как упоминалось в комментариях, почему следующие исключения не вызывают исключения?

return val == null ? null : "true".equalsIgnoreCase(val);

Ну, вышеприведенная выдержка из спецификации специфически применима только к булевым условным выражениям, которые указаны здесь (§15.25):

Если оба выражения второго и третьего операндов являются булевыми выражениями, условное выражение является булевым условным выражением.

В целях классификации условного выражения следующие выражения являются булевыми выражениями:

  • Выражение отдельной формы (§15.2), имеющее тип Boolean или Boolean.

  • В скобках выражение Boolean (§15.8.5).

  • Выражение экземпляра экземпляра класса (§15.9) для класса Boolean.

  • Вызов вызова метода (§15.12), для которого выбранный наиболее специфический метод (§15.12.2.5) имеет тип возврата Boolean или Boolean.
    (Обратите внимание, что для универсального метода это тип перед созданием аргументов типа метода.)

  • A Boolean условное выражение.

Так как null не является булевым выражением, общее условное выражение не является булевым условным выражением. Ссылаясь на таблицу 15.2 (позже в том же разделе), мы можем видеть, что такое выражение считается типом Boolean, поэтому не происходит распаковки, и исключение не возникает.

Ответ 2

val == null ? defVal : "true".equalsIgnoreCase(val) - в этом выражении третий аргумент boolean, и поскольку тернарный оператор должен иметь один статический тип, он попытается распаковать null, следовательно, NPE. Только назначение boolean приведет к боксу снова. Проверьте этот.

Ответ 3

Потому что autounboxing. Java выводит общий тип для defVal и "true".equalsIgnoreCase(val). Тип первого - логический, но второй логический. По неизвестной причине он считает, что общий тип будет логическим (вы можете найти правило в спецификации).

Ответ 4

Это можно объяснить, если defVal null.

С тройными выражениями оба параметра должны быть одного типа, и если не применяется какое-либо принуждение:

JLS 15.25 "Условный оператор?:" говорит:

Если один из второго и третьего операндов имеет примитивный тип T, а тип другого - результат применения преобразования бокса (п. 5.1.7) в T, то тип условного выражения T.

В случае этого выражения:

val == null ? defVal : "true".equalsIgnoreCase(val)

Значение Boolean для defVal автоматически распаковывается в соответствии с результатом Boolean сравнения строк. Это несмотря на то, что результат тройного затем автоматически помещается в квадрат обратно к Boolean - при принятии решения о том, как бросать, тройной не считает ничего вне себя.

Ответ 5

Ok. Попробуйте таким образом

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

private static Boolean withTernary(String val, Boolean defVal){
  return (val == null ? defVal : Boolean.valueOf("true".equalsIgnoreCase(val)));
}

Теперь все будет хорошо. Теперь здесь нет unboxing и вы не получите исключения. Но в противном случае из-за null unbox вы получите NPE

public static void main(String[] args){
    System.out.println(withTernary(null, null)); //Null Pointer
}

private static Boolean withTernary(String val, Boolean defVal){
    return (val == null ? defVal : "true".equalsIgnoreCase(val));
}