2017-02-20 2 views
56

В Java, это не представляется возможным создать массив универсального типа непосредственно:Constructor ссылка - не предупреждение, когда массив дженерики создается

Test<String>[] t1 = new Test<String>[10]; // Compile-time error 

Однако, мы можем сделать это с помощью сырого типа:

Test<String>[] t2 = new Test[10]; // Compile warning "unchecked" 

в Java 8, также возможно использовать ссылку конструктора:

interface ArrayCreator<T> { 
    T create(int n); 
} 

ArrayCreator<Test<String>[]> ac = Test[]::new; // No warning 
Test<String>[] t3 = ac.create(10); 

Почему не компилятор d проигнорировать предупреждение в последнем случае? Он по-прежнему использует тип raw для создания массива, не так ли?

+4

смешно ... писать 'n -> новый тест [n];' вместо 'Test [] :: new' (который в основном должен быть тем же) снова даст вам предупреждение о непроверенной компиляции – Roland

+2

@ Roland они оба отшлифованы до того же самого точного байтового кода. Действительно интересно – Eugene

+1

@Eugene: ну, для формальных правил, как правило, не имеет значения, чего скрывают языковые конструкции. Но компилятор явно должен выпустить предупреждение * непроверенное * здесь. – Holger

ответ

28

Ваш вопрос обоснован. Короче говоря, ссылка на метод действительно использует исходный тип (или должен использовать необработанный тип), и причина, почему создание общих массивов запрещена, по-прежнему применяется при использовании ссылок на методы, следовательно, возможность тихо создавать функцию, создающую общий массив явно нарушает намерение дизайна языка.

Причина, по которой создание общего массива запрещена, заключается в том, что наследование типа массива, связанное с эрой до Generics, несовместимо с системой типового типа. То есть Вы можете написать:

IntFunction<List<String>[]> af = List[]::new; // should generate warning 
List<String>[] array = af.apply(10); 
Object[] objArray = array; 
objArray[0] = Arrays.asList(42); 
List<String> list = array[0]; // heap pollution 

На этом месте, следует подчеркнуть, что в отличие от некоторых ответов здесь, компилятор делает не выполнить определение типа на выражении List[]::new вывести общий тип List<String> элемента. Это легко доказать, что родовое создание массива до сих пор запрещено:

IntFunction<List<String>[]> af = List<String>[]::new; // does not compile 

Поскольку List<String>[]::new является незаконным, было бы странно, если бы List[]::new был принят без предупреждения путем выводя его эффективно незаконной List<String>[]::new быть.

JLS §15.13 ясно сказано:

Если метод опорного выражение имеет вид ArrayType::new, то ArrayType должен обозначать тип, который reifiable (§4.7), или ошибка во время компиляции имеет место.

Это уже означает, что List<String>[]::new является незаконным, потому что List<String> не reifiable, в то время как List<?>[]::new является законным, так как List<?> является reifiable и List[]::new является законным, если мы считаем List быть сырье типа, как сырье типList можно отменить.

Тогда §15.13.1 гласит:

Если метод опорного выражение имеет вид ArrayType::new, один смысловой метод считается. Метод имеет единственный параметр типа int, возвращает ArrayType и не имеет условия throws. Если n = 1, это единственный потенциально применимый метод; в противном случае нет потенциально применимых методов.

Другими словами, поведение выражения List[]::new выше такой же, как если бы вы написали:

IntFunction<List<String>[]> af = MyClass::create; 
… 
private static List[] create(int i) { 
    return new List[i]; 
} 

за исключением того, что метод create только отвлеченный. И действительно, с этим явным объявлением метода есть только необработанные типы предупреждений по методу create, но нет непроверено предупреждения о преобразовании List[] в List<String>[] по ссылке метода. Поэтому понятно, что происходит в компиляторе в случае List[]::new, где метод, использующий необработанные типы, является только условным, т. Е. Не существует в исходном коде.

Но отсутствие непроверенных предупреждения является явным нарушением JLS §5.1.9, Unchecked Conversion:

Пусть G имя по шаблонного типа с п параметров типа.

Существует бесконтрольно преобразование из сырья класса или интерфейса типа (§4.8) G к любому типу параметризованной формы G<T₁,...,Tₙ>.

Существует необработанное преобразование из типа необработанного массива G[]ᵏ в любой тип массива формы G<T₁,...,Tₙ>[]ᵏ. (Обозначение []ᵏ указывает тип массива из к размеров.)

Использование непроверенного преобразования вызывает время компиляции непроверенную предупреждение если все аргументы типа T ᵢ (1 ≤ яп) являются неограниченными подстановочными знаками (§4.5.1), или непроверенное предупреждение подавляется аннотацией SuppressWarnings (§9.6.4.5).

Таким образом, преобразование List[] в List<?>[] является законным, так как List спараметрирован с неограниченным символом, но преобразование из List[] в List<String>[] должно произвести непроверенной предупреждения, что имеет решающее значение здесь, как использование List[]::new не производит необработанный тип предупреждение, которое появляется с явным методом создания. Отсутствие необработанного типа предупреждений, кажется, не является нарушением (насколько я понял §4.8), и это не будет проблемой, если javac создал необходимое предупреждение , непроверенное.

+0

Довольно хороший анализ. Я все еще думаю, что вывод типа является причиной отсутствия предупреждения. Но я согласен с тем, что не следует выводить тип. –

+3

Интересно, что это предупреждение всегда игнорируется, когда неконтролируемое преобразование происходит из необработанного метода возвращаемого типа в параметризованный тип целевого интерфейса, если вы используете ссылку на метод. +1 –

+1

jdk-9, build 157 - то же поведение 'non-warning' – Eugene

10

Самое лучшее, что я могу придумать, что JLS указывает, что эталонный метод конструктору универсального типа выводит общие параметры: «Если метод или конструктор родовое, соответствующие аргументы типа могут быть либо выведено или представлено явно ». Позже он дает в качестве примера ArrayList::new и описывает его как «аргументы аргумента типа для общего класса», тем самым устанавливая, что ArrayList::new (а не ArrayList<>::new) является синтаксисом, который содержит аргументы.

Учитывая класс:

public static class Test<T> { 
    public Test() {} 
} 

это дает предупреждение:

Test<String> = new Test(); // No <String> 

, но это не делает:

Supplier<Test<String>> = Test::new; // No <String> but no warning 

потому что Test::new неявно выводит общие аргументы.

Так что я предполагаю, что ссылка метода на конструктор массива работает одинаково.

+1

https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13 «Если метод или конструктор является общим, могут быть выведены соответствующие аргументы типа или в явном виде.« Позже он дает ArrayList :: new в качестве примера и описывает его как« аргументы аргумента типа для общего класса », тем самым устанавливая, что« ArrayList :: new »(а не« ArrayList <> :: new ») является синтаксисом –

+1

Хорошая точка, добавлено –

+1

Но если аргумент типа выведен, это будет означать вызов нового теста [] ', который является ошибкой компилятора, так как нет возможности повторить проверку' Test '. И _if_ он все еще заканчивает вызов' new Test [] '(что является моим предположением), этот тип вывода не является хорошим объяснением того, почему нет предупреждения imho. –

5

Он по-прежнему использует исходный тип для создания массива, не так ли?

Генераторы Java - это всего лишь иллюзия времени компиляции, поэтому исходный тип, конечно, будет использоваться во время выполнения для создания массива.

Почему компилятор не отображает предупреждение в последнем случае?

Да, непроверенный литой от Test[] до Test<String>[] все еще происходит; это просто происходит за кулисами в анонимном контексте.

Test<String>[] t3 = ((IntFunction<Test<String>[]>) Test[]::new).apply(10); 

Поскольку anonymous method делает грязную работу, неконтролируемый бросок эффективно исчезает из управляемого кода.

+0

Это похоже на мой первоначальный ответ, но я не думаю, что он действительно отвечает на вопрос. Несмотря на то, что «грязная работа» скрыта, эффект все тот же, и вопрос в том, почему компилятор не выдавал предупреждение в этом случае. Я считаю, что правильный ответ (который я изложил ниже) заключается в том, что «новый тест [n]» ссылается на тест типа raw, а ссылка метода Test [] :: new указывает на аргументы общего типа. –

+3

@WillisBlackburn Я не эксперт, но я думаю, что вывод типа конструктора не является основной причиной. В конце концов, мы говорим о массивах здесь. Полагаю, нам придется подождать, пока эксперт не весит. –

Смежные вопросы