W3docs

Java Reflection: вызов конструкторов

Создавайте объекты Java рефлективно с помощью Constructor.newInstance и Class.getDeclaredConstructor.

Создание объекта без new — это рефлективный приём, лежащий в основе каждого контейнера внедрения зависимостей, десериализатора и загрузчика плагинов: у вас есть Class, и вам нужен экземпляр. Объект Constructor<T> представляет один конструктор и создаёт экземпляры с помощью newInstance(args...). В этой главе рассматривается поиск конструкторов, их вызов с аргументами, доступ к private-конструкторам и причины, по которым устаревший ярлык Class.newInstance() помечен как deprecated.

Если вы только начинаете знакомство с reflection, начните с введения в reflection, а затем возвращайтесь сюда. Механика ниже повторяет то, что вы видели при вызове методов и чтении полей рефлективно.

Поиск конструкторов

Конструкторы ищутся только по типам параметров — имени у них нет, так как все конструкторы класса разделяют его имя:

Class<User> c = User.class;

Constructor<User> noArg  = c.getDeclaredConstructor();                  // ()
Constructor<User> twoArg = c.getDeclaredConstructor(String.class, int.class);  // (String, int)

Constructor<?>[] pub  = c.getConstructors();          // public only
Constructor<?>[] all  = c.getDeclaredConstructors();  // any access level

Как и везде в reflection, типы параметров должны совпадать точно (int.class, а не Integer.class), и getConstructor видит только public-конструкторы, тогда как getDeclaredConstructor видит и private/protected/пакетные. Обратите внимание: Constructor<T> обобщён по классу, который строит, поэтому newInstance возвращает типизированный T (в отличие от сырого Object у Method.invoke).

Создание экземпляров с помощью newInstance

Constructor<User> ctor = User.class.getDeclaredConstructor(String.class, int.class);
User u = ctor.newInstance("ada", 36);     // returns a typed User

Аргументы работают так же, как в Method.invoke: varargs Object[], примитивы автоупаковываются. Отличие в том, что здесь нет целевого объекта — конструктор создаёт цель. Исключения, брошенные телом конструктора, оборачиваются в InvocationTargetException, точно как с методами; разворачивайте через getCause().

Доступ к private-конструкторам

Синглтоны, утилитные классы и строители нередко скрывают конструктор. Reflection легко обходит это с помощью setAccessible(true):

Constructor<Singleton> ctor = Singleton.class.getDeclaredConstructor();
ctor.setAccessible(true);                 // bypass the private modifier
Singleton fresh = ctor.newInstance();     // a SECOND instance — breaks the singleton!

Это действительно мощно и действительно опасно: это ломает гарантию синглтона, контракт «без экземпляров» утилитного класса и любой инвариант, который защищал конструктор. (enum-синглтон — единственная форма, которую reflection не может инстанциировать: Constructor.newInstance явно отказывается работать с типами enum, бросая IllegalArgumentException, что и является одной из причин, почему «enum-синглтон» — рекомендуемый паттерн.)

Почему Class.newInstance() помечен как deprecated

В старом коде вы встретите ярлык clazz.newInstance():

User u = User.class.newInstance();   // DEPRECATED since Java 9

Он помечен как deprecated по двум реальным причинам:

  1. Вызывает только no-arg конструктор. Нет способа передать аргументы.
  2. Некорректно обрабатывает исключения. Если no-arg конструктор бросает проверяемое исключение, Class.newInstance() распространяет его без объявления — обходя проверку проверяемых исключений компилятором.

Замена всегда одна:

User u = User.class.getDeclaredConstructor().newInstance();

Это на одну строку длиннее, вызывает явно выбранный вами конструктор и оборачивает его исключения в InvocationTargetException, так что ничего не просачивается необъявленным. Используйте это как стандартную идиому даже для no-arg случая.

Проработанный пример: небольшая рефлективная фабрика

Программа создаёт объекты тремя способами: публичный конструктор с несколькими аргументами, private-конструктор, доступный через setAccessible, и современная no-arg идиома — затем показывает, как конструктор, бросающий исключение, оборачивается, а также сигнатуру устаревшего ярлыка для сравнения.

java— editable, runs on the server

Что следует вынести из запуска:

  • Универсальная фабрика build создала Widget и Hidden из Class плюс массив типов параметров — не называя ни один тип в выражении new. Эта сигнатура, <T> T build(Class<T>, Class<?>[], Object...), по сути и есть то, как выглядит ядро инстанциирования в DI-контейнере: передаёте тип и аргументы, получаете экземпляр.
  • getDeclaredConstructor().newInstance() создал Widget по умолчанию, демонстрируя современную замену для Class.newInstance(). Всегда предпочитайте его: он позволяет выбрать конструктор и направляет его исключения через InvocationTargetException, а не допускает утечки необъявленных проверяемых исключений.
  • Рефлективный экземпляр Hidden не совпадал с объектом Hidden.INSTANCE (same instance? false). setAccessible(true) беспрепятственно прошёл мимо private-конструктора и создал второй экземпляр — конкретное доказательство того, что reflection может разрушить основную гарантию синглтона. Защищённые синглтоны бросают исключение из конструктора, если экземпляр уже существует; enum-ы иммунны по своей природе.
  • Конструктор, отвергший отрицательный размер, бросил IllegalArgumentException из своего тела, и это всплыло как InvocationTargetException с реальной причиной внутри — идентичная обёртка, как в Method.invoke. Валидация в момент построения сохраняется через reflection; просто нужно развернуть, чтобы её увидеть.
  • Constructor<T> вернул типизированный T (Widget, Hidden) без приведения типа, в отличие от сырого Object у Method.invoke. Поскольку конструктор обобщён по классу, который строит, фабрика остаётся типобезопасной на своей границе, даже если внутри всё рефлективно.

Практика

Практика
Коллега пишет 'MyService s = MyService.class.newInstance();', а линтер помечает 'newInstance()' как deprecated. Какова рекомендуемая замена и какова главная практическая причина устаревания старой формы?
Коллега пишет 'MyService s = MyService.class.newInstance();', а линтер помечает 'newInstance()' как deprecated. Какова рекомендуемая замена и какова главная практическая причина устаревания старой формы?
Was this page helpful?