Ссылка на конструктор - нет предупреждения при создании массива generics


в Java невозможно создать массив универсального типа напрямую:

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

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

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);

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

3 55

3 ответа:

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

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

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 был принят без a предупреждение, делая вывод, что это фактически незаконно List<String>[]::new.

JLS §15.13 четко указано:

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

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

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

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

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

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

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

но отсутствие unchecked предупреждения-это явное нарушение JLS §5.1.9, непроверенное преобразование:

пусть G имя a объявление универсального типа с n параметры типа.

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

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

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

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

лучшее, что я могу придумать, это то, что 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 неявно выводит типовые аргументы.

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

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

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

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

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

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

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