Что препятствует обобщённому коду создать экземпляр параметра типа через обычный конструктор?
Обобщённый код не может создать экземпляр T через обычный вызов конструктора, потому что параметр типа не является конкретным классом, доступным для такой проверки и создания. После стирания типов информация о T обычно отсутствует во время выполнения, а сам параметр может обозначать интерфейс, абстрактный класс или тип без подходящего конструктора.
Обычно проблему решают передачей фабрики, Supplier<T> или иного явно предоставленного механизма создания объектов.
Generics в Java проектировались как средство статической проверки типов с сохранением совместимости с кодом и библиотеками, написанными до появления параметризации. Поэтому значительная часть информации о параметрах типов используется компилятором, но не сохраняется как полноценная runtime-информация.
Такой подход позволяет обобщённому коду работать с разными типами без создания отдельной runtime-версии класса для каждой параметризации. Компромисс состоит в том, что операции, требующие знания конкретного типа во время выполнения, нельзя выразить одним только параметром T.
Пусть метод должен вернуть новый объект типа T. Наивное решение предполагает, что у любого возможного T есть доступный конструктор без аргументов и что его можно вызвать во время выполнения.
Оба предположения неверны. T может быть интерфейсом, абстрактным классом, классом с приватным или параметризованным конструктором, а верхняя граница типа описывает допустимые операции, но не гарантирует наличие конкретного конструктора.
Параметр типа задаёт ограничения для компилятора, но не предоставляет объект Class<T> и не является выражением, у которого можно вызвать конструктор. Даже ограничение вроде T extends Number не определяет, какой именно конструктор нужно вызвать: разные подтипы Number имеют разные правила создания.
Кроме того, после стирания типов метод фактически работает с верхней границей T, а при отсутствии явной границы — с Object. Компилятор не может корректно сгенерировать универсальный вызов конструктора для всех разрешённых подстановок.
Надёжный вариант — передать ответственность за создание объекта вызывающему коду:
Здесь обобщённый класс не пытается угадать способ создания. Фабрика уже знает конкретный тип и его конструктор, а Supplier<? extends T> позволяет передать поставщик самого T или его подтипа.
Передача Class<T> может быть полезна для отражения, но это не универсальное решение: нужный конструктор может отсутствовать, быть недоступным или требовать аргументы. Вызовы reflection также хуже проверяются компилятором и обычно требуют обработки исключений.
Предположим, инфраструктурный компонент должен создавать экземпляры обработчиков, выбранных конфигурацией. Вариант с попыткой создать T напрямую невозможен. Вариант с Class<T> позволяет использовать reflection, но связывает компонент с правилами конструкторов и переносит ошибки на выполнение программы.
Более устойчивое решение — принимать фабрику Supplier<? extends T> или специализированный интерфейс фабрики с параметрами. Это явно фиксирует зависимости создания, хорошо тестируется через подмену фабрики и поддерживает классы, которым нужны аргументы конструктора. Недостаток — вызывающий код должен заранее передать дополнительный объект, но это делает контракт честным и устраняет скрытое требование о конструкторе без аргументов.
Нет. Верхняя граница гарантирует только принадлежность типа указанному классу или интерфейсам и возможность использовать операции, объявленные в этих границах. Она не гарантирует конкретный конструктор, потому что конструкторы не наследуются и не являются частью полиморфного контракта типа.
Class<T>?Иногда, но Class<T> лишь передаёт runtime-представление типа. Код всё равно должен выбрать конструктор, проверить его доступность и передать необходимые аргументы. Для классов с разными способами создания или сложными зависимостями явная фабрика обычно безопаснее и выразительнее.
Информация о параметризации иногда записывается в метаданных Signature и доступна через reflection для объявлений классов, методов и полей. Однако это не превращает T в конкретный runtime-класс внутри обобщённого метода и не даёт возможности автоматически вызвать его конструктор. Для создания нужен явно переданный объект фабрики, Class<T> или другой runtime-механизм.