Рассмотрим, что такое рефлексия в Java, какие возможности предоставляет API и как ее можно использовать в проектах. В статье приведем несколько примеров кода, которые можно запустить и посмотреть на результат, разберем схемы работы и особенности использования. Код проверенно работает с Java 11. Чтобы разобраться в методах рефлексии, потребуется практический опыт работы с Java.
Приобрести его можно на курсе Skypro «Java-разработчик». Программу составляли опытные разработчики, поэтому практических заданий там много, а теории только необходимый минимум. Результаты практических заданий можно использовать в качестве портфолио, чтобы быстрее найти работу по новой специальности.
Что такое Java Reflection API
Java Reflection — это особенный функционал, который позволяет программе получить доступ к приватным частям объектов или поменять поведение некоторых методов классов. Созданный таким образом код будет адаптироваться к входным данным и, например, не будет зависеть от типов, с которыми работает.
Это дает возможность писать код, который со временем будет эволюционировать, то есть не зависеть от текущих имплементаций методов или переменных. Главные преимущества рефлексии — свобода и адаптивность. При необходимости вызвать приватный метод класса можно не переписывать его, а вызвать через Java Reflection. Фактически рефлексия позволяет не следовать написанному коду, вводя новые правила. Можно пойти чуть дальше и начать перехватывать вызовы методов, подменяя их другой логикой, или создать программу, которая будет работать с еще не написанным классом.
Для чего используется рефлексия
Примеров, когда рефлексия становится полезной в проектах, множество. Рассмотрим несколько вариантов ее использования:
- При тестировании кода. Часто бывает нужно проверить корректность работы приватной функции, однако в тесте ее вызвать не получается именно из-за того, что она приватная. Вариантов решения задачи два — сделать ее на время публичной, а потом обратно приватной, или просто вызвать ее в тесте через рефлексию. Второй вариант намного проще и быстрее.
- При написании фреймворков и библиотек. В популярном Spring Framework рефлексия используется для создания бинов. Во время работы программы Spring Framework собирает данные о классах, помеченных аннотацией `@Component`, и создает для них экземпляры. Это позволяет создавать бины без явного указания их в конфигурационном файле.
- Для поиска и запуска тестов. Например, так применяет рефлексию библиотека JUnit. Опытные пользователи замечали, что тесты помечены аннотацией `@Test`. Это сделано как раз для того, чтобы во время работы JUnit прошелся по всем классам и запустил всё с этой аннотацией.
- Для сериализации и десериализации объектов. Например, библиотека Jackson использует рефлексию для сериализации и десериализации объектов в стандарте JSON. Без нее Jackson не смог бы прочитать значения приватных полей и корректно сохранить их в JSON-формате. То же касается и десериализации, когда Jackson должен восстановить значения всех полей, в том числе и приватных, — это было бы невозможно без рефлексии.
Больше узнать об этих библиотеках и научиться программировать поможет курс по Java онлайн-университета Skypro. Он включает 440 часов теории и практики, видеоуроки и вебинары с преподавателями — экспертами в области Java-разработки, а также мастер-классы с реальными рабочими задачами и поддержку наставников. После окончания курса студенты получают диплом о профессиональной переподготовке.
Особенности Java Reflection
Нужно понимать, что Java Reflection API — это часть языка, а не библиотеки. Это означает, что использовать рефлексию можно над любым классом, написанными на Java. Для этого достаточно импортировать пакет `java.lang.reflect` в свой код.
Стоит отметить, что рефлексия в Java является довольно медленной, поэтому ее стоит использовать при отсутствии других вариантов. Причина — большинство операций не определены до выполнения программы, что мешает оптимизации кода в ходе компиляции. Рефлексия использует динамическую загрузку классов, что также требует еще больше ресурсов.
Рефлексия в Java не поддерживается с примитивными типами данных, такими как, например, int. Чтобы использовать рефлексию с ними, придется создать классы-обертки вокруг них.
История рефлексии в Java
Само понятие рефлексии в Java было введено почти с самого начала существования этого языка программирования, в версии 1.1. До этого можно было работать только предопределенными классами.
В Java 1.1 появился класс `Class`. Именно он позволяет получить данные о классе, к которому принадлежит объект. Таким образом стало возможным узнать, какие методы существуют у класса, и вызвать их. Для этого не нужно инициировать `Class` с нужным классом.
```java Class<?> stringClass = String.class; Method[] methods = stringClass.getMethods(); ```
Пример инициации класса `String` и получения всех методов `String`
С тех пор Java постоянно улучшала рефлексию, добавляя новые возможности. Например, в Java 5 появился новый оператор `instanceof`, который позволяет проверить, является ли объект экземпляром класса. А в Java 8 появился метод `getDeclaredMethods()`, который позволяет получить информацию о методах класса включая приватные.
Динамические прокси
Версия рефлексии в Java обладает еще одним очень полезным свойством — позволяет создавать динамические прокси. А они, в свою очередь, позволяют перехватывать вызовы к методам выбранного класса. Это может пригодиться, например, если нужно логировать вызовы к методам класса.
Примеры использования Java Reflection
Рассмотрим несколько имплементаций того, как можно использовать рефлексию в Java. Все примеры будут использовать класс `Human`, в котором есть приватное поле `name` и публичный геттер `getName()`, а также приватная функция `generateSecret()`. Приватная функция берет слово Secret и добавляет к нему входную строку, которую мы называем `salt`. Затем генерируется соответствующее строке число и выдается как результат.
Класс `Human` приведен ниже:
```java public class Human { public String name; public Human(String name, String address) { this.name = name; this.address = address; } private int generateSecret(String salt) { return "secret".concat(salt).hashCode(); } public String getName() { return name; } } ```
Получение метаданных класса
Ключевым для рефлексии является класс `Class`, именно он хранит всю информацию о классе, для которого был инициирован. Эти данные называют метаданными класса, то есть это вся доступная с помощью рефлексии информация о нем. Например, в метаданные входят имя класса, его модификаторы, родительский класс, интерфейсы, конструкторы, поля, методы и так далее.
В качестве примера создания класса Class для Human создадим класс `ReflectionExample` и в методе `main()` создадим объект класса `Human`. Для того чтобы получить соответствующий Class, достаточно вызвать функцию `getClass()` на любом объекте:
```java public class ReflectionExample { public static void main(String[] args) { Human john = new Human("John", "London"); Class<?> humanClass = john.getClass(); } } ```
Теперь мы можем использовать переменную `humanClass` для того, чтобы получить конкретные данные о классе. Для этого можно вызвать следующие методы на объекте `humanClass`:
* `getName()` — возвращает имя и пакет класса;
* `getSimpleName()` — возвращает имя класса без пакета;
* `getModifiers()` — возвращает модификаторы класса;
* `getSuperclass()` — возвращает родительский класс;
* `getInterfaces()` — возвращает список интерфейсов, которые наследует класс;
* `getConstructors()` — возвращает список конструкторов класса;
* `getFields()` — возвращает список публичных полей класса;
* `getDeclaredFields()` — возвращает список всех полей класса, в том числе приватных;
* `getMethods()` — возвращает массив публичных методов класса;
* `getDeclaredMethods()` — возвращает массив всех методов класса, в том числе приватных;
* `getPackage()` — возвращает имя пакета класса.
В методах `getFields()` и `getMethods()` заключается основной функционал рефлексии. Именно они позволят нам в дальнейшем поменять приватные поля и вызвать приватные методы.
Узнать о других методах и классах в языке и научиться их использовать можно на курсе Skypro «Java-разработчик». Программа обучения разбита на тематические блоки, в конце которых студенты выполняют курсовую работу. Опытные наставники и кураторы всегда готовы ответить на вопросы и помочь разобрать сложный материал.
Получение метаданных переменной
Покажем, как получить доступ и поменять то, что хранится в приватной переменной `name` в классе `Human`, даже если у нее нет сеттера.
Так же, как и выше, создадим объект класса `Human` и соответствующий `Class`. Используем функцию `getDeclaredFields()` для получения всех, в том числе приватных полей класса. Далее пройдемся по массиву переменных и найдем ту, которую хотим поменять. Как только мы нашли переменную, можем сразу же поменять или прочитать ее значение. Для этого используем функцию `setAccessible()` для снятия ограничения доступа и `set()` для изменения или `get()` для чтения значения.
```java public class ReflectionVarExample { public static void main(String[] args) throws Exception { Human john = new Human("John", 25); Field[] flds = Human.class.getDeclaredFields(); for (Field fld : flds) { if (fld.getName().equals("name")) { fld.setAccessible(true); fld.set(john, "Ivan"); } } System.out.println(john.getName()); } } ```
Если запустить этот код, можно увидеть, что имя изменилось на `Bob`, хотя изначально было `John`.
Получение метаданных метода
Теперь посмотрим, как получить метаданные обо всех методах класса, в том числе и приватных, а также вызвать любой из них. В Java Reflection методы можно получить сходным образом — это значит, что примеры получения метаданных метода и переменной во многом будут похожи.
Как и раньше, создадим объект класса `Human` и получим его метаданные. Далее используем `getDeclaredMethods` и получим список всех методов в этом классе. Среди этого списка найдем нужный и вызовем этот метод с собственными данными. Используем функцию `setAccessible()` для снятия ограничения доступа и `invoke()` непосредственно для вызова.
```java public class ReflectionMethodExample { public static void main(String[] args) throws Exception { Human john = new Human("John", 25); Method[] mthds = Human.class.getDeclaredMethods(); for (Method mthd : mthds) { if (mthd.getName().equals("getSecret")) { mthd.setAccessible(true); String secret = (int) mthd.invoke(john, "broken"); System.out.println(secret); } } } } ```
После запуска данной программы мы получим число из метода `getSecret`, несмотря на то что он был обозначен как приватный и нигде в коде класса не использовался.
Освойте Java на курсе онлайн-университета Skypro. Учебе нужно будет уделять всего 10 часов в неделю: сможете совмещать с работой и личными делами. Вас ждут 440 часов теории и практики, мастер-классы с реальными рабочими задачами, опытные преподаватели, кураторы и наставники. За время обучения сделаете два полноценных проекта для портфолио и множество домашних заданий, чтобы отработать навыки.
Краткие итоги
Рефлексия в Java — многофункциональный и мощный инструмент. Он помогает программистам создавать различные фреймворки и библиотеки, позволяет писать гибкий код. Однако с рефлексией нужно работать с осторожностью, так как она может сильно замедлить работу программы.
Добавить комментарий