Булевы, условные операторы и автобокс - PullRequest
129 голосов
/ 07 октября 2010

Почему этот бросок NullPointerException

public static void main(String[] args) throws Exception {
    Boolean b = true ? returnsNull() : false; // NPE on this line.
    System.out.println(b);
}

public static Boolean returnsNull() {
    return null;
}

пока это не

public static void main(String[] args) throws Exception {
    Boolean b = true ? null : false;
    System.out.println(b); // null
}

Решение заключается в том, чтобы заменить false на Boolean.FALSE, чтобы избежать распаковки null в boolean - что невозможно. Но это не вопрос. Вопрос почему ? Есть ли в JLS ссылки, подтверждающие это поведение, особенно во втором случае?

Ответы [ 4 ]

90 голосов
/ 07 октября 2010

Разница в том, что явный тип метода returnsNull() влияет на статическую типизацию выражений во время компиляции:

E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean)

E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)

См. Спецификация языка Java, раздел 15.25 Условный оператор?:

  • Для E1 типы 2-го и 3-го операндов соответственно Boolean и boolean, поэтому применяется этот пункт:

    Если один из второго и третьего операндов имеет тип boolean, а тип другого имеет тип Boolean, то тип условного выражения является логическим.

    Поскольку типвыражение boolean, второй операнд должен быть приведен к boolean.Компилятор вставляет код автоматической распаковки во второй операнд (возвращаемое значение returnsNull()), чтобы он набрал boolean.Это, конечно, вызывает NPE из null, возвращаемого во время выполнения.

  • Для E2 типы 2-го и 3-го операндов равны <special null type> (не Boolean, как вE1!) И boolean соответственно, поэтому не применяется специальное предложение о наборе текста ( go read 'em! ), поэтому применяется последнее предложение «иначе»:

    В противном случаевторой и третий операнды имеют типы S1 и S2 соответственно.Пусть T1 будет типом, который получается в результате применения преобразования в бокс для S1, и пусть T2 будет типом, который получается в результате применения преобразования в бокс в S2.Тип условного выражения является результатом применения преобразования захвата (§5.1.10) к lub (T1, T2) (§15.12.2.7).

    • S1 == <special null type> (см. §4.1 )
    • S2 == boolean
    • T1 == box (S1) == <special null type> (см. последний элемент в списке боксапреобразования в §5.1.7 )
    • T2 == box (S2) == `Boolean
    • lub (T1, T2) == Boolean

    Таким образом, тип условного выражения - Boolean, а третий операнд должен быть приведен к Boolean.Компилятор вставляет код автобокса для 3-го операнда (false).2-й операнд не нуждается в автоматическом распаковывании, как в E1, поэтому не нужно автоматически распаковывать NPE при возвращении null.


Этот вопрос требует аналогичногоанализ типа:

условный оператор Java?: тип результата

24 голосов
/ 07 октября 2010

Строка:

    Boolean b = true ? returnsNull() : false;

внутренне преобразуется в:

    Boolean b = true ? returnsNull().booleanValue() : false; 

для выполнения распаковки;таким образом: null.booleanValue() даст NPE

Это одна из главных ловушек при использовании автобокса.Это поведение действительно задокументировано в 5.1.8 JLS

Редактировать: я полагаю, что распаковка происходит из-за того, что третий оператор имеет логический тип, например (добавлено неявное приведение):

   Boolean b = (Boolean) true ? true : false; 
16 голосов
/ 07 октября 2010

С Спецификация языка Java, раздел 15.25 :

  • Если один из второго и третьего операнды типа булева и тип другого типа Boolean, тогда тип условного выражение логическое.

Итак, первый пример пытается вызвать Boolean.booleanValue(), чтобы преобразовать Boolean в boolean согласно первому правилу.

Во втором случае первый операнд имеет нулевой тип, тогда как второй операнд не относится к ссылочному типу, поэтому применяется автоматическое преобразование:

  • В противном случае, второй и третий операнды имеют типы S1 и S2 соответственно. Пусть T1 будет типом, который результаты от применения бокса преобразование в S1, и пусть T2 будет тип, который является результатом применения бокса преобразование в S2. Тип условное выражение является результатом применения захвата преобразования (§5.1.10) в lub (T1, T2) (§15.12.2.7).
0 голосов
/ 24 марта 2016

Мы можем увидеть эту проблему по байт-коду. В строке 3 байтового кода main, 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z, логическое значение бокса со значением null, invokevirtual метод java.lang.Boolean.booleanValue, конечно, будет выброшен NPE.

    public static void main(java.lang.String[]) throws java.lang.Exception;
      descriptor: ([Ljava/lang/String;)V
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=2, locals=2, args_size=1
           0: invokestatic  #2                  // Method returnsNull:()Ljava/lang/Boolean;
           3: invokevirtual #3                  // Method java/lang/Boolean.booleanValue:()Z
           6: invokestatic  #4                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
           9: astore_1
          10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
          13: aload_1
          14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          17: return
        LineNumberTable:
          line 3: 0
          line 4: 10
          line 5: 17
      Exceptions:
        throws java.lang.Exception

    public static java.lang.Boolean returnsNull();
      descriptor: ()Ljava/lang/Boolean;
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=1, locals=0, args_size=0
           0: aconst_null
           1: areturn
        LineNumberTable:
          line 8: 0
...