Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

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 должен обозначать тип, который можно повторно идентифицировать (§4.7), или возникает ошибка времени компиляции.

Это уже означает, что List<String>[]::new является незаконным, поскольку List<String> не может быть повторно идентифицируемым, тогда как List<?>[]::new является законным, поскольку List<?> является допустимым, а List[]::new является законным, если мы рассматриваем List как raw type, так как сырой тип 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, где метод с использованием типов raw является только условным, т.е. Не существует в исходном коде.

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

Пусть G укажите объявление общего типа с параметрами типа n.

Существует непроверенное преобразование из исходного класса или типа интерфейса (§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>[] должно приводить к неконтролируемому предупреждению, что имеет решающее значение здесь, так как использование List[]::new не создает предупреждение типа raw, которое появляется с явным методом создания. Отсутствие необработанных предупреждений типа не является нарушением (насколько я понял §4.8), и это не было бы проблемой, если javac создало требуемое непроверенное предупреждение.

Ответ 2

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

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

Ответ 3

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

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

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

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

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

Так как анонимный метод