При создании обобщённого объекта через diamond-оператор от чего зависит фактически выведенный аргумент типа?
Фактически выведенный аргумент типа зависит от контекста выражения: прежде всего от целевого типа присваивания или параметра метода, а при его отсутствии — от аргументов конструктора и доступных границ. Diamond-оператор не означает автоматический выбор универсального типа без правил вывода.
Diamond-оператор появился, чтобы убрать повторное указание одинаковых аргументов типа при создании объектов. До него тип приходилось писать и слева, и справа, что увеличивало шум и риск расхождения параметризаций.
Развитие вывода типов сделало выражения создания объектов контекстно-зависимыми: компилятор может использовать тип, ожидаемый в месте выражения. При этом стирание типов не отменяет проверку параметризации на этапе компиляции.
Одинаково выглядящие выражения с diamond могут получить разные аргументы типа. Особенно легко ошибиться при использовании локального вывода типа: если целевой тип отсутствует, компилятор не обязан угадывать тип, который разработчик имел в виду.
Неверный вывод приводит не к немедленной ошибке создания объекта, а к несовместимости позже — например, при передаче коллекции в метод, ожидающий более конкретную параметризацию. Это может проявиться далеко от места объявления.
В присваивании целевой тип обычно участвует в выводе:
Для a ожидаемый тип слева позволяет вывести String. Вызов accept(new ArrayList<>()) также получает контекст List<String> от параметра метода. У b такого контекста нет, поэтому при пустом конструкторе выводится наиболее общий подходящий тип — обычно Object; это уже ArrayList<Object>, а не ArrayList<String>.
Если конструктор принимает значения, они тоже участвуют в выводе. Например, наличие строкового аргумента может привести к выводу String, но результат всё равно проверяется с учётом целевого типа, ограничений параметров и совместимости всех аргументов.
Diamond выводит аргументы типа только во время компиляции. В байткоде обычный параметризованный объект использует стирание типов, поэтому runtime не получает отдельный класс для каждого варианта параметризации. Однако вставленные компилятором проверки и приведения обеспечивают типобезопасность исходной программы.
Компромисс заключается между краткостью и явностью. Diamond удобен при очевидном контексте, а явное указание типа предпочтительнее, когда переменная объявляется через var, конструктор пуст, либо параметризация важна для читаемости API.
В сервисе разработчик объявил рабочий список через var и diamond, рассчитывая использовать его как список идентификаторов. Позже список передали в метод, принимающий List<String>, но компилятор отверг вызов: локальная переменная уже имела тип ArrayList<Object>.
Рассматривались три варианта. Явно записать ArrayList<String> надёжно, но длиннее; заменить var на List<String> лучше скрывает реализацию и задаёт нужный контекст; оставить var можно только при явном типовом аргументе справа, что сохраняет краткость, но раскрывает конкретный класс.
Выбрали List<String>, потому что переменная использовалась через интерфейс коллекции, а не через особенности ArrayList. Ошибка исчезла, намерение стало видимым, а реализацию можно менять без изменения типа переменной.
Нет. Это возможно, когда выражение находится в контексте, предоставляющем ожидаемый тип. При использовании var целевой тип для инициализатора не задаётся: переменная получает тип, выведенный из самого выражения.
Object?Не всегда. Если контекст задаёт String, результатом будет параметризация String, а не Object. Object появляется лишь как наиболее общий вывод в ситуации без более конкретных ограничений, например при пустом конструкторе без целевого типа.
Нет. Он влияет только на статическую типизацию исходного выражения и последующие проверки компилятора. После стирания типов JVM обычно видит один класс, например ArrayList, независимо от того, был ли аргументом типа String, Integer или другой ссылочный тип.