Что означают аргументы типа конструктора при размещении * перед * типом?

Я недавно столкнулся с этим необычным (для меня) синтаксисом Java... вот пример этого:

List list = new <String, Long>ArrayList();

Обратите внимание на расположение аргументов типа <String, Long>... это не после типа, как обычно, а до. Я не против признать, что никогда раньше не видел этот синтаксис. Также обратите внимание, что есть 2 аргумента типа, когда ArrayList имеет только 1.

Позиционирование аргументов типа имеет то же значение, что и размещение их после типа? Если нет, что означает другое позиционирование?

Почему допустимо иметь 2 аргумента типа, когда ArrayList имеет только 1?

Я искал обычные места, например. Анжелика Лангер и здесь, но не может найти никакого упоминания об этом синтаксисе где-либо кроме правил грамматики в файле грамматики Java в проекте ANTLR.

Ответы

Ответ 1

Вызов универсального конструктора

Это необычно хорошо, но полностью действительна Java. Чтобы понять, нам нужно знать, что класс может иметь универсальный конструктор, например:

public class TypeWithGenericConstructor {

    public <T> TypeWithGenericConstructor(T arg) {
        // TODO Auto-generated constructor stub
    }

}

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

    new TypeWithGenericConstructor(LocalDate.now(ZoneId.systemDefault()));

Теперь T явно LocalDate. Однако могут быть случаи, когда Java не может вывести (вывести) аргумент типа. Затем мы предоставляем это явно, используя синтаксис из вашего вопроса:

    new <LocalDate>TypeWithGenericConstructor(null);

Конечно, мы можем также предоставить его, даже если в этом нет необходимости, если мы думаем, что это помогает удобочитаемости или по какой-либо причине:

    new <LocalDate>TypeWithGenericConstructor(LocalDate.now(ZoneId.systemDefault()));

В вашем вопросе вы, кажется, вызываете конструктор java.util.ArrayList. Этот конструктор не является универсальным (только класс ArrayList в целом, это что-то еще). Почему Java позволяет вам предоставлять аргументы типа в вызове, когда они не используются, см. Мое редактирование ниже. Мое Затмение дает мне предупреждение:

Неиспользуемые аргументы типа для неуниверсального конструктора ArrayList() типа ArrayList; это не должно быть параметризовано с аргументами

Но это не ошибка, и программа работает нормально (я дополнительно получаю предупреждения об отсутствующих аргументах типа для List и ArrayList, но это опять-таки другая история).

Универсальный класс против универсального конструктора

Позиционирование аргументов типа имеет то же значение, что и размещение их после типа? Если нет, что означает другое позиционирование?

Нет, это другое. Обычный аргумент типа /s после типа (ArrayList<Integer>()) предназначен для универсального класса. Аргументы типа перед предназначены для конструктора.

Две формы также могут быть объединены:

    List<Integer> list = new <String, Long>ArrayList<Integer>();

Я бы посчитал это немного более правильным, так как теперь мы можем видеть, что в списке хранятся объекты Integer (я бы, конечно, предпочел опустить бессмысленный <String, Long>).

Почему допустимо иметь 2 аргумента типа, когда ArrayList имеет только 1?

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

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

Отредактируйте с помощью @Slaw за ссылку: Java позволяет вводить аргументы типа во всех вызовах методов. Если вызываемый метод является универсальным, используются аргументы типа; если нет, они игнорируются. Например:

    int length = "My string".<List>length();

Да, это абсурд. Спецификация языка Java (JLS) дает это обоснование в подразделе 15.12.2.1:

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

Аргумент не распространяется на конструкторы, так как они не могут быть напрямую переопределены. Но я полагаю, что они хотели иметь одно и то же правило, чтобы не усложнять и без того сложные правила. В любом случае, раздел 15.9.3 об экземплярах и new экземплярах более одного раза ссылается на 15.12.2.

связи

Ответ 2

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

new <Long>String();
Thread.currentThread().<Long>getName();

Компилятору все равно, потому что он не должен сопоставлять аргументы этих типов с фактическими общими параметрами.

Как только компилятор должен проверить аргументы, он жалуется на несоответствие:

Collections.<String, Long>singleton("A"); // does not compile

Похоже, ошибка компилятора для меня.