Я считаю, что это была ошибка, которая, кажется, была исправлена. Согласно JLS, выбрасывание NullPointerException
кажется правильным поведением.
Я думаю, что здесь происходит то, что по какой-то причине в версии 8 компилятор учитывал границы переменной типа, упомянутые возвращаемым типом метода, а не фактические аргументы типа. Другими словами, он думает, что ...get("1")
возвращает Object
. Это может быть связано с тем, что он рассматривает возможность стирания метода или по какой-либо другой причине.
Поведение должно зависеть от типа возвращаемого значения метода get
, как указано в приведенных ниже выдержках из 15.26:
Если и второе, и третье выражения операнда являются числовыми выражениями, условное выражение является числовым условным выражением.
В целях классификации условного выражения следующие выражения являются числовыми выражениями:
[]
Выражение вызова метода (§15.12), для которого выбранный наиболее конкретный метод (§15.12.2.5) имеет возвращаемый тип, преобразуемый в числовой тип.
Обратите внимание, что для универсального метода это тип перед созданием экземпляра аргументов типа метода.
[]
В противном случае условное выражение является ссылочным условным выражением.
[]
Тип числового условного выражения определяется следующим образом:
Другими словами, если оба выражения могут быть преобразованы в числовой тип, и одно является примитивным, а другое упакованным, то тип результата тернарного условного выражения является примитивным типом.
(Таблица 15.25-C также удобно показывает нам, что тип тернарного выражения boolean ? double : Double
действительно будет double
, что опять же означает правильность распаковки и выбрасывания.)
Если тип возвращаемого значения метода get
не может быть преобразован в числовой тип, тогда троичное условное выражение будет считаться «ссылочным условным выражением», и распаковка не произойдет.
Кроме того, я думаю, что примечание "для универсального метода это тип перед созданием экземпляра аргументов типа метода" не должно применяться к нашему случаю. Map.get
не объявляет переменные типа, так что это не общий метод по определению JLS. Однако это примечание было добавлено в Java 9 (это единственное изменение, см. JLS8), поэтому вполне возможно, что это как-то связано с поведением, которое мы наблюдаем сегодня.
Для HashMap<String, Double>
возвращаемый тип get
должен быть Double
.
Вот MCVE, поддерживающий мою теорию о том, что компилятор рассматривает границы переменной типа, а не фактические аргументы типа:
class Example<N extends Number, D extends Double> {
N nullAsNumber() { return null; }
D nullAsDouble() { return null; }
public static void main(String[] args) {
Example<Double, Double> e = new Example<>();
try {
Double a = false ? 0.0 : e.nullAsNumber();
System.out.printf("a == %f%n", a);
Double b = false ? 0.0 : e.nullAsDouble();
System.out.printf("b == %f%n", b);
} catch (NullPointerException x) {
System.out.println(x);
}
}
}
Вывод этой программы на Java 8:
a == null
java.lang.NullPointerException
Другими словами, несмотря на то, что e.nullAsNumber()
и e.nullAsDouble()
имеют один и тот же тип возвращаемого значения, только e.nullAsDouble()
считается "числовым выражением". Единственная разница между методами заключается в привязке переменной типа.
Вероятно, можно было бы провести больше исследований, но я хотел бы опубликовать свои выводы. Я пробовал довольно много вещей и обнаружил, что ошибка (т.е. без распаковки/NPE), по-видимому, возникает только тогда, когда выражение является методом с переменной типа в возвращаемом типе.
Интересно, что я обнаружил, что следующая программа также генерирует в Java 8:
import java.util.*;
class Example {
static void accept(Double d) {}
public static void main(String[] args) {
accept(false ? 1.0 : new HashMap<String, Double>().get("1"));
}
}
Это показывает, что поведение компилятора на самом деле различается в зависимости от того, присваивается ли тернарное выражение локальной переменной или параметру метода.
(Первоначально я хотел использовать перегрузки, чтобы доказать фактический тип, который компилятор дает троичному выражению, но это не похоже на то, что это возможно, учитывая указанную выше разницу. Возможно, есть еще один способ, о котором я не подумал, хотя.)
person
Radiodef
schedule
09.06.2018
false ? 1.0 : (Double) null
выдаетNPE
для обоих JDK (JDK 1.8.0_144 и JDK 10.0.1), а версияnew HashMap<...>...
выдает только для JDK 10.0.1. - person Turing85   schedule 09.06.2018double
, поэтому он должен быть распакован. - person Radiodef   schedule 09.06.2018