W3docs

Введение в Java Reflection

Что такое рефлексия в Java, когда её применять и обзор пакета java.lang.reflect.

Рефлексия — это способность программы исследовать и изменять собственную структуру во время выполнения: узнавать, какие поля, методы и конструкторы есть у класса, читать и записывать эти поля, вызывать методы и создавать новые экземпляры — не называя типы во время компиляции. Если обычный Java-код статичен (компилятор знает каждый тип, с которым вы работаете), то рефлексия динамична (типы обнаруживаются из строк, конфигурации или того, что загружается во время выполнения). Эта часть книги посвящена java.lang.reflect; данная глава задаёт общий контекст.

Что позволяет делать рефлексия

Обычный код явно называет тип, с которым работает:

User u = new User("ada");
String name = u.getName();

Рефлексивный код достигает того же результата, не записывая User или getName как токены времени компиляции — это строки, разрешаемые во время выполнения:

Class<?> cls = Class.forName("com.example.User");
Object u = cls.getDeclaredConstructor(String.class).newInstance("ada");
Object name = cls.getMethod("getName").invoke(u);

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

Когда рефлексия оправдывает себя

Рефлексия — двигатель фреймворков, а не повседневного кода. Типичные применения:

  • Внедрение зависимостей (Spring, Guice, CDI): контейнер читает аннотации и сигнатуры конструкторов, затем создаёт и связывает бины, исходный код которых он никогда не видел.
  • Сериализация (Jackson, Gson): JSON-библиотека обходит поля объекта для чтения или заполнения без необходимости писать код маппинга для каждого класса.
  • ORM (Hibernate, JPA): отображает столбцы на поля, рефлексивно обходя класс-сущность.
  • Тестовые раннеры (JUnit): находят методы с аннотацией @Test и вызывают их.
  • Плагин-системы: загружают класс, имя которого указано в файле конфигурации, и вызывают на нём метод известного интерфейса.

Общая нить: универсальный механизм, работающий над произвольными пользовательскими типами, против которых он не был скомпилирован. Именно эту задачу и решает рефлексия.

Основные типы в java.lang.reflect

Рефлексия начинается с объекта Class (рассматривается в следующей главе, Java Class Objects) и разветвляется в небольшое семейство классов, каждый из которых описывает один вид члена:

ТипПредставляетПолучение из Class
FieldполеgetField / getDeclaredField(s)
MethodметодgetMethod / getDeclaredMethod(s)
Constructor<T>конструкторgetConstructor / getDeclaredConstructor(s)
Parameterпараметр метода/конструктораExecutable.getParameters()
Modifierвспомогательный класс для битового набора модификаторов intстатические методы

Field, Method и Constructor разделяют общую иерархию суперклассов: Member (интерфейс) и AccessibleObject (содержащий setAccessible). Именно благодаря этой общей базе операции «сделать доступным» и «прочитать аннотации» выглядят одинаково вне зависимости от того, с каким членом вы работаете.

Разделение get… и getDeclared…

Почти каждый метод поиска существует в двух вариантах, и это различие важно в каждой последующей главе:

  • getField / getMethod / getConstructor — возвращает публичные члены, включая унаследованные из суперклассов и интерфейсов.
  • getDeclaredField / getDeclaredMethod / getDeclaredConstructor — возвращает члены с любым уровнем доступа (private, protected, пакетным), но только те, что объявлены в данном конкретном классе — никаких унаследованных.

Таким образом, getMethods() видит публичный метод, унаследованный от родителя, но не private-вспомогательный метод самого класса; getDeclaredMethods() видит private-вспомогательный метод, но не унаследованный публичный. Чтобы добраться до приватного унаследованного члена, нужно пройти по getSuperclass(), вызывая getDeclared… на каждом уровне.

Цена: производительность, безопасность и инкапсуляция

Рефлексия мощна, и каждая возможность имеет свою цену.

  • Производительность. Рефлексивные вызовы медленнее прямых — поиск метода/поля, проверки доступа и боксинг аргументов добавляют накладные расходы. JIT хорошо оптимизирует часто используемые рефлексивные вызовы, но рефлексия в жёстком цикле — признак проблемы. Кэшируйте объекты Method/Field; никогда не ищите их при каждом вызове.
  • Отсутствие безопасности на этапе компиляции. Опечатка в имени метода компилируется нормально и вызывает ошибку во время выполнения с NoSuchMethodException. Инструменты рефакторинга переименуют getName везде — кроме строки "getName" в вашем коде.
  • Нарушение инкапсуляции. setAccessible(true) позволяет читать и записывать private-состояние. Именно так сериализаторы заполняют поля без сеттеров, но это связывает вас с внутренними деталями, стабильность которых автор класса никогда не гарантировал.
  • Ограничения модулей. Начиная с Java 9, система модулей может запрещать рефлексивный доступ к неэкспортируемым пакетам. Вызов setAccessible(true) через границу модуля, который не opens данный пакет, выбрасывает InaccessibleObjectException.

Практический пример: миниатюрный универсальный дампер объектов

Чтобы нагляднее показать область применения, вот простая рефлексивная процедура, выводящая поля любого объекта и их значения — именно то, что делает отладчик или библиотека логирования. Она не называет ни одного прикладного типа и работает с любым переданным объектом.

java— editable, runs on the server

Что важно вынести из примера:

  • Метод dump не называл ни одного конкретного типа, но тем не менее вывел и Point, и User. Его единственный контракт — «передайте мне Object»; всё о структуре — имена полей, типы, значения — поступило из getClass() во время выполнения. Это и есть определяющий приём рефлексии: одна процедура, произвольные входные данные.
  • getDeclaredFields() вернул все поля, включая private, но для их чтения сначала потребовался вызов setAccessible(true). Без него f.get(obj) для приватного поля выбрасывает IllegalAccessException. Поиск и доступ — два отдельных уровня защиты.
  • Modifier.toString(f.getModifiers()) преобразовал исходный битовый набор модификаторов в читаемый текст вида private final. Модификаторы хранятся как int с флаговыми битами; вспомогательный класс Modifier декодирует их, избавляя от ручной проверки битов.
  • Третий объект был создан без единого new User(...) в исходном коде — Class.forName("…$User") разрешил вложенный класс из строки (обратите внимание на разделитель $ для вложенных типов), а getDeclaredConstructor(...).newInstance(...) сконструировал его. Это паттерн загрузки плагинов в миниатюре: имя на входе — объект на выходе.
  • Чтение значения поля (f.get(obj)) и чтение метаданных поля (f.getName(), f.getModifiers()) независимы друг от друга. Для метаданных не нужен экземпляр и флаг доступности; для значений нужен объект, а для приватных полей — ещё и флаг доступности.

Структура этой части

Каждая из оставшихся глав рассматривает один из аспектов:

  • Объекты класса — три способа получить Class<T> и что он сообщает.
  • Поля — изучение, чтение и запись полей (включая private и final).
  • Методы — поиск и вызов методов, разрешение перегрузок, возвращаемые значения.
  • Конструкторы — создание экземпляров рефлексивно, включая приватные конструкторы.
  • Аннотации — чтение метаданных, добавленных с помощью аннотаций, во время выполнения.
  • Динамические прокси — синтез целых реализаций интерфейсов во время выполнения.

Всегда помните о компромиссе: рефлексия — правильный инструмент, когда тип действительно неизвестен до момента выполнения, и неправильный — когда он известен. Следующая глава начинается с корня всего — Class-объекта.

Практика

Практика
Библиотека логирования должна выводить значения полей любого объекта, переданного в её метод 'log(Object o)', включая объекты классов, против которых она не была скомпилирована, с полями 'private'. Какое сочетание является минимально верным подходом?
Библиотека логирования должна выводить значения полей любого объекта, переданного в её метод 'log(Object o)', включая объекты классов, против которых она не была скомпилирована, с полями 'private'. Какое сочетание является минимально верным подходом?
Was this page helpful?