11 Ноя 2022
8 мин
6197

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

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

Содержание

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

Содержание

Добавить комментарий

Определи профессию по рисунку
Пройдите тест, узнайте какой профессии подходитеНачать тест
+