Введение в 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.
Практический пример: миниатюрный универсальный дампер объектов
Чтобы нагляднее показать область применения, вот простая рефлексивная процедура, выводящая поля любого объекта и их значения — именно то, что делает отладчик или библиотека логирования. Она не называет ни одного прикладного типа и работает с любым переданным объектом.
Что важно вынести из примера:
- Метод
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-объекта.