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 по двум реальным причинам:
- Вызывает только no-arg конструктор. Нет способа передать аргументы.
- Некорректно обрабатывает исключения. Если no-arg конструктор бросает проверяемое исключение,
Class.newInstance()распространяет его без объявления — обходя проверку проверяемых исключений компилятором.
Замена всегда одна:
User u = User.class.getDeclaredConstructor().newInstance();Это на одну строку длиннее, вызывает явно выбранный вами конструктор и оборачивает его исключения в InvocationTargetException, так что ничего не просачивается необъявленным. Используйте это как стандартную идиому даже для no-arg случая.
Проработанный пример: небольшая рефлективная фабрика
Программа создаёт объекты тремя способами: публичный конструктор с несколькими аргументами, private-конструктор, доступный через setAccessible, и современная no-arg идиома — затем показывает, как конструктор, бросающий исключение, оборачивается, а также сигнатуру устаревшего ярлыка для сравнения.
Что следует вынести из запуска:
- Универсальная фабрика
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. Поскольку конструктор обобщён по классу, который строит, фабрика остаётся типобезопасной на своей границе, даже если внутри всё рефлективно.