Reflection в Java: временно выходим из правил языка
Reflection часто описывают как API, через который можно узнать поля класса, найти метод по имени или вызвать private-конструктор. Такое описание верное, но почти ничего не объясняет.
Главная идея reflection связана с устройством JVM.
После компиляции Java-код превращается в class-файлы с метаданными о типах, методах, полях, модификаторах и связях между классами. Во время загрузки классов JVM собирает из этих данных модель выполняемой программы.
Без нее JVM не смогла бы определить, какую реализацию size() вызвать здесь:
List values = getValues(); values.size();
Тип объекта и иерархия его класса известны JVM во время выполнения. Reflection дает Java-коду доступ к части этих метаданных.
Например, мы можем загрузить класс, найти конструктор и вызвать метод, хотя конкретный тип не был известен во время компиляции:
Class type = Class.forName(className);
Constructor constructor = type.getConstructor(); Object instance = constructor.newInstance();
Method method = type.getMethod(“process”, String.class); Object result = method.invoke(instance, “data”);
На этой возможности построены dependency injection, ORM, сериализация, тестовые библиотеки и многие другие механизмы Java-экосистемы.
Но у такой гибкости есть цена.
При обычном вызове компилятор заранее проверяет тип объекта, сигнатуру метода, аргументы и доступность. В рефлексивном коде значительная часть этих проверок переносится в runtime.
Из-за этого появляются знакомые особенности Core Reflection API:
- метод ищется по имени и массиву типов аргументов - параметры передаются как Object[] - примитивы требуют boxing и unboxing - результат Method.invoke() возвращается как Object - ошибка внутри вызываемого метода оборачивается в InvocationTargetException - проблемы с сигнатурой или доступом обнаруживаются во время выполнения - setAccessible(true) позволяет обойти часть правил инкапсуляции
Можно сказать, что рефлексивный код написан на Java, но работает по правилам динамической среды JVM. Многие гарантии статической типизации в этот момент исчезают.
Насколько reflection медленнее обычного вызова?
Универсального коэффициента здесь нет.
Рефлексивный вызов может включать создание массива аргументов, boxing, проверки доступа и дополнительные уровни косвенного вызова. JIT-компилятору также сложнее анализировать такой call site и выполнять inlining.
Результат зависит от конкретного кода:
- ищется ли Method при каждом вызове или хранится в кеше - известен ли JVM конкретный target - насколько часто выполняется вызов - сколько работы делает вызываемый метод - какая версия JDK используется - находится ли reflection в горячем участке приложения
Разница хорошо заметна в цикле, где миллионы раз вызывается крошечный getter. В коде инициализации Spring-контекста или сериализации объекта рядом с сетевым запросом затраты могут потеряться на фоне остальной работы.
Поэтому бенчмарк вида «рефлекшн медленнее на 23%» почти ничего не говорит о производительности приложения. Микробенчмарк всегда выдаст число. Вопрос в том, описывает ли это число реальную нагрузку.
Есть и исторический нюанс. До Java 18 reflection сначала использовал native-вызовы, а после достижения порога генерировал специальный bytecode accessor. Начиная с Java 18 реализация Method, Constructor и Field переведена на method handles. Публичный API сохранился, внутренний механизм изменился. Подробнее об этом можно прочитать в JEP 416:
Если хочется глубже разобраться в reflection, у Бена Эванса есть две отличные статьи. Первая объясняет модель и API, вторая разбирает внутреннюю реализацию и влияние рефлекшн вызовов на производительность:
Reflection for the modern Java programmer https://blogs.oracle.com/javamagazine/java-reflection-introduction/
The performance implications of Java reflection https://blogs.oracle.com/javamagazine/java-reflection-performance/
· вчера
Не часто приходилось реализовывать фичи на чистом reflecrion. Одна из самых универсальных api в java. Нет вру ... самая
ответить
коммент удалён
· вчера
В целом - да. Я использовал при реализации астрактной логики, когда не знаешь с какими обьектами рабатаешь. Поэтому я и написал в посте с акцентом про фреймворки и либы. В бизнесовом коде нам чаще нужны типизация и определенность. Но интересно что сам рефлекшн приводит нас к коду где нет фиксированных типов
ответить
ответ удалён