Найти тему
JavAKnazzz

Java Reflection API: методы и примеры использования

Оглавление
Что такое Java Reflection и для чего используется

Рассмотрим, что такое рефлексия в Java, какие возможности предоставляет API и как ее можно использовать в проектах. В статье приведем несколько примеров кода, которые можно запустить и посмотреть на результат, разберем схемы работы и особенности использования.

Что такое Java Reflection API

Java Reflection — это особенный функционал, который позволяет программе получить доступ к приватным частям объектов или поменять поведение некоторых методов классов. Созданный таким образом код будет адаптироваться к входным данным и, например, не будет зависеть от типов, с которыми работает.

Это дает возможность писать код, который со временем будет эволюционировать, то есть не зависеть от текущих имплементаций методов или переменных. Главные преимущества рефлексии — свобода и адаптивность. При необходимости вызвать приватный метод класса можно не переписывать его, а вызвать через Java Reflection. Фактически рефлексия позволяет не следовать написанному коду, вводя новые правила. Можно пойти чуть дальше и начать перехватывать вызовы методов, подменяя их другой логикой, или создать программу, которая будет работать с еще не написанным классом.

Для чего используется рефлексия

Примеров, когда рефлексия становится полезной в проектах, множество. Рассмотрим несколько вариантов ее использования:

  • При тестировании кода. Часто бывает нужно проверить корректность работы приватной функции, однако в тесте ее вызвать не получается именно из-за того, что она приватная. Вариантов решения задачи два — сделать ее на время публичной, а потом обратно приватной, или просто вызвать ее в тесте через рефлексию. Второй вариант намного проще и быстрее.
  • При написании фреймворков и библиотек. В популярном Spring Framework рефлексия используется для создания бинов. Во время работы программы Spring Framework собирает данные о классах, помеченных аннотацией `@Component`, и создает для них экземпляры. Это позволяет создавать бины без явного указания их в конфигурационном файле.
  • Для поиска и запуска тестов. Например, так применяет рефлексию библиотека JUnit. Опытные пользователи замечали, что тесты помечены аннотацией `@Test`. Это сделано как раз для того, чтобы во время работы JUnit прошелся по всем классам и запустил всё с этой аннотацией.
  • Для сериализации и десериализации объектов. Например, библиотека Jackson использует рефлексию для сериализации и десериализации объектов в стандарте JSON. Без нее Jackson не смог бы прочитать значения приватных полей и корректно сохранить их в JSON-формате. То же касается и десериализации, когда Jackson должен восстановить значения всех полей, в том числе и приватных, — это было бы невозможно без рефлексии.

Особенности Java Reflection

Нужно понимать, что Java Reflection API — это часть языка, а не библиотеки. Это означает, что использовать рефлексию можно над любым классом, написанными на Java. Для этого достаточно импортировать пакет `java.lang.reflect` в свой код.

Стоит отметить, что рефлексия в Java является довольно медленной, поэтому ее стоит использовать при отсутствии других вариантов. Причина — большинство операций не определены до выполнения программы, что мешает оптимизации кода в ходе компиляции. Рефлексия использует динамическую загрузку классов, что также требует еще больше ресурсов.

Рефлексия в Java не поддерживается с примитивными типами данных, такими как, например, int. Чтобы использовать рефлексию с ними, придется создать классы-обертки вокруг них.

История рефлексии в Java

Само понятие рефлексии в Java было введено почти с самого начала существования этого языка программирования, в версии 1.1. До этого можно было работать только предопределенными классами.

В Java 1.1 появился класс `Class`. Именно он позволяет получить данные о классе, к которому принадлежит объект. Таким образом стало возможным узнать, какие методы существуют у класса, и вызвать их. Для этого не нужно инициировать `Class` с нужным классом.

Класс Class в Java представляет собой объект, который представляет определенный класс или интерфейс во время выполнения программы. Этот класс позволяет получать информацию о классе, создавать новые экземпляры класса, получать информацию о полях, методах и конструкторах класса, а также работать с аннотациями.

Основные методы класса Class:

1. getName(): возвращает имя класса в виде строки.
Пример:
Class<?> clazz = String.class;
System.out.println(clazz.getName()); // Выведет "java.lang.String"


2. newInstance(): создает новый экземпляр класса.
Пример:
Class<?> clazz = String.class;
String str = (String) clazz.newInstance();


3. getFields(): возвращает массив public полей класса.
Пример:
Class<?> clazz = String.class;
Field[] fields = clazz.getFields();


4. getMethods(): возвращает массив public методов класса.
Пример:
Class<?> clazz = String.class;
Method[] methods = clazz.getMethods();


Класс Class используется для рефлексии, то есть для получения информации о классе во время выполнения программы. Он позволяет создавать экземпляры классов динамически, вызывать их методы и работать с их полями. Класс Class также используется в различных библиотеках и фреймворках, где требуется динамическое создание объектов или обработка аннотаций.

С тех пор Java постоянно улучшала рефлексию, добавляя новые возможности. Например, в Java 5 появился новый оператор `instanceof`, который позволяет проверить, является ли объект экземпляром класса. А в Java 8 появился метод `getDeclaredMethods()`, который позволяет получить информацию о методах класса включая приватные.

Динамические прокси

Версия рефлексии в Java обладает еще одним очень полезным свойством — позволяет создавать динамические прокси. А они, в свою очередь, позволяют перехватывать вызовы к методам выбранного класса. Это может пригодиться, например, если нужно логировать вызовы к методам класса.

Получение метаданных класса

Ключевым для рефлексии является класс `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&lt;?&gt; 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`, несмотря на то что он был обозначен как приватный и нигде в коде класса не использовался.