Что такое рефлексия в java
Перейти к содержимому

Что такое рефлексия в java

  • автор:

Аннотации и рефлексия в Java

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

Аннотации в Java

Аннотации в Java представляют собой метаданные, которые можно добавлять к классам, методам, полям и другим элементам кода. Они обеспечивают дополнительную информацию о коде, которая может быть использована компилятором, средствами разработки или даже во время выполнения программы. Для создания аннотации в Java используется аннотированный интерфейс, который определяет структуру аннотации.

Пример объявления аннотации:

public @interface MyAnnotation < String value(); // Элемент аннотации >

В данном примере MyAnnotation — это пользовательская аннотация, содержащая один элемент value . Элементы аннотации могут иметь различные типы данных, такие как строки, числа или даже другие классы.

Встроенные аннотации (например, Override, Deprecated)

Java предоставляет набор встроенных аннотаций, которые имеют специальное значение и часто используются в разработке.

— @Override : Эта аннотация указывает, что метод переопределяет метод из суперкласса. Она помогает предотвратить ошибки в случае, если вы ошибочно не переопределили метод.

Пример использования @Override :

@Override public void someMethod() < // Код метода >

— @Deprecated : Эта аннотация помечает элемент (класс, метод, поле и т. д.) как устаревший. Она предупреждает разработчиков о том, что использование этого элемента не рекомендуется, и в будущих версиях Java может быть удалено.

Пример использования @Deprecated :

@Deprecated public void oldMethod() < // Устаревший код >

Использование встроенных аннотаций помогает улучшить читаемость и надежность кода, а также упрощает его документирование. Они также могут быть использованы средствами анализа кода или инструментами для генерации документации.

Создание пользовательских аннотаций

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

Документирование кода

Пользовательские аннотации могут использоваться для документирования вашего кода, добавляя дополнительные комментарии и описания. Например, вы можете создать аннотацию @Description , чтобы добавить краткое описание класса, метода или переменной, которое автоматически включается в сгенерированную документацию:

@Retention(RetentionPolicy.RUNTIME) @Target() public @interface Description
@Description("Этот класс представляет собой модель пользователя.") public class User < // Поля и методы класса >

Проверка валидности данных

Аннотации могут использоваться для проверки валидности данных на этапе компиляции или даже во время выполнения. Например, вы можете создать аннотацию @NotNull , которая гарантирует, что поле не может быть пустым:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface NotNull
public class User < @NotNull private String username; // Остальные поля и методы >

Автоматизация задач

Пользовательские аннотации могут использоваться для автоматизации различных задач. Например, вы можете создать аннотацию @Benchmark , которая помечает метод как метод, который нужно измерить на производительность.

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Benchmark
public class PerformanceTester < @Benchmark public void testMethod() < // Код для тестирования производительности >// Другие методы >

Шаги создания пользовательской аннотации

Создание пользовательской аннотации в Java включает в себя несколько шагов:

1. Определение аннотации:
— Определите интерфейс с ключевым словом @interface . В этом интерфейсе определяются элементы аннотации, которые могут иметь различные типы данных.

2. Определение целевых элементов:
— Решите, на какие элементы вашего кода вы хотите применять аннотацию (классы, методы, поля и т. д.). Это определяется с помощью аннотации @Target .

3. Указание правил видимости:
— Определите, как долго аннотация будет храниться (зависит от вашей потребности): во время компиляции, во время выполнения или вообще не храниться. Это устанавливается с помощью аннотации @Retention .

4. Применение аннотации:
— Примените вашу пользовательскую аннотацию к соответствующим элементам вашего кода, используя синтаксис @НазваниеАннотации .

5. Обработка аннотаций (по желанию):
— Разработайте код или инструменты, которые будут анализировать и использовать информацию из ваших пользовательских аннотаций.

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

Применение аннотаций

1. Маркировка элементов:

Аннотации могут быть использованы для маркировки классов, методов, полей и других элементов кода. Например, аннотация @Entity может быть применена к классу, чтобы указать, что это сущность базы данных.

@Entity public class User < // Поля и методы >

2. Дополнительная информация:

Аннотации могут содержать дополнительные элементы, которые предоставляют информацию или параметры. Например, аннотация @Column может указывать дополнительные настройки для столбца в базе данных.

@Column(name = "user_name", nullable = false) private String username;

3. Контроль компиляции:

Аннотации могут использоваться для контроля компиляции. Например, аннотация @Override гарантирует, что метод действительно переопределен из суперкласса.

@Override public void someMethod() < // Код метода >

4. Аннотации времени выполнения:

Некоторые аннотации могут использоваться во время выполнения. Например, аннотация @Autowired в Spring Framework используется для инъекции зависимостей во время выполнения.

@Autowired private UserService userService;

5. Обработка аннотаций:

Средства анализа кода и библиотеки могут анализировать аннотации и выполнять дополнительные действия в зависимости от их наличия. Например, библиотека JUnit использует аннотации для определения тестовых методов и их автоматического выполнения.

@Test public void testMethod() < // Код теста >

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

Рефлексия в Java

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

Рефлексия имеет важное значение в различных аспектах разработки Java:

a. Анализ кода на этапе выполнения:
— Рефлексия позволяет приложению анализировать классы, методы и поля во время выполнения, что особенно полезно в контексте рефлексии для анализа аннотаций, создания макетов данных и т. д.

b. Интроспекция библиотек и фреймворков:
— Многие библиотеки и фреймворки Java используют рефлексию для сканирования и обработки классов и ресурсов. Это позволяет им создавать мощные и гибкие решения, такие как инверсия управления (Inversion of Control) и внедрение зависимостей (Dependency Injection).

c. Создание обобщенных утилит:
— Рефлексия позволяет создавать обобщенные утилиты, которые могут работать с различными классами и объектами, даже если их структура заранее неизвестна.

Как рефлексия отличается от статического кода

Рефлексия и статический код представляют собой два различных подхода к работе с данными и кодом в Java:

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

Класс Class и объекты Class

Класс Class в Java представляет собой метаданные о классе, интерфейсе или примитивном типе данных.

Существуют несколько способов получения объекта Class :

a. С использованием литерала класса:

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

Class myClassClass = MyClass.class;

b. С использованием метода getClass():

В Java у каждого объекта есть метод getClass() , который возвращает объект Class , представляющий тип этого объекта. Этот метод может быть полезен при работе с объектами, когда тип объекта известен только во время выполнения.

MyClass obj = new MyClass(); Class objClass = obj.getClass();

c. С использованием статического метода forName():

Метод Class.forName(String className) позволяет загрузить класс по его имени в виде строки. Этот метод полезен, когда имя класса известно во время выполнения и может быть задано динамически.

String className = "com.example.MyClass"; Class myClassClass = Class.forName(className);

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

Класс Class предоставляет множество методов для анализа и взаимодействия с типами данных. Вот некоторые из наиболее часто используемых методов:

a. getName()
— Метод getName() возвращает имя класса в виде строки.

Class myClassClass = MyClass.class; String className = myClassClass.getName(); // Возвращает "com.example.MyClass"

b. getSimpleName():
— Метод getSimpleName() возвращает простое имя класса (без пакета) в виде строки.

String simpleName = myClassClass.getSimpleName(); // Возвращает "MyClass"

c. isAssignableFrom(Class cls):
— Метод isAssignableFrom(Class cls) проверяет, может ли класс, представляемый текущим объектом Class , быть присвоен классу, представляемому объектом cls .

boolean isAssignable = Number.class.isAssignableFrom(Integer.class); // Возвращает true

d. getMethods(), getFields(), getConstructors():
— Методы getMethods() , getFields() , getConstructors() возвращают массив методов, полей и конструкторов соответственно, которые доступны в данном классе (включая унаследованные).

Method[] methods = myClassClass.getMethods(); Field[] fields = myClassClass.getFields(); Constructor[] constructors = myClassClass.getConstructors();

Эти методы и многие другие позволяют анализировать и взаимодействовать с классами и объектами во время выполнения.

Использование рефлексии

1. Получение информации о классе

Рефлексия в Java позволяет получать различную информацию о классе, такую как его имя, пакет, суперкласс и интерфейсы. Вот некоторые способы получения информации о классе с использованием рефлексии:

a. Получение имени класса:
— Вы можете получить имя класса с помощью метода getName() класса Class .

Class myClassClass = MyClass.class; String className = myClassClass.getName(); // Возвращает "com.example.MyClass"

b. Получение имени пакета:
— Можно извлечь имя пакета, в котором находится класс, с помощью метода getPackage() .

Package classPackage = myClassClass.getPackage(); String packageName = classPackage.getName(); // Возвращает "com.example"

c. Получение суперкласса:
— С помощью метода getSuperclass() можно получить суперкласс текущего класса.

Class superClass = myClassClass.getSuperclass();

d. Получение интерфейсов:
— Вы можете получить список интерфейсов, которые реализует класс, с помощью метода getInterfaces() .

Class[] interfaces = myClassClass.getInterfaces();

Создание объектов с использованием рефлексии

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

a. Создание объекта без параметров:
— С помощью метода newInstance() класса Class можно создать объект класса без передачи параметров конструктору.

Class myClassClass = MyClass.class; MyClass instance = (MyClass) myClassClass.newInstance();

b. Создание объекта с параметрами:
— Если у класса есть конструктор с параметрами, вы можете получить этот конструктор и передать аргументы с помощью рефлексии.

Class myClassClass = MyClass.class; Constructor constructor = myClassClass.getConstructor(String.class, int.class); MyClass instance = (MyClass) constructor.newInstance("example", 42);

Вызов методов и чтение полей с помощью рефлексии

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

a. Вызов метода:
— Вы можете получить метод класса с помощью метода getMethod() , а затем вызвать его, передав необходимые аргументы.

Class myClassClass = MyClass.class; Method method = myClassClass.getMethod("someMethod", int.class); MyClass instance = new MyClass(); int result = (int) method.invoke(instance, 42);

b. Чтение поля:
— Для чтения значения поля класса используйте метод getField() и метод get() объекта поля.

Class myClassClass = MyClass.class; Field field = myClassClass.getField("fieldName"); MyClass instance = new MyClass(); String value = (String) field.get(instance);

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

Аннотации и рефлексия в совместной работе

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

1. Использование аннотаций для маркировки классов и методов

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

@MyCustomAnnotation public class MyClass < @MyCustomAnnotation public void myMethod() < // Реализация метода >>

2. Извлечение аннотаций и выполнение действий с помощью рефлексии

Рефлексия позволяет анализировать аннотации, примененные к классам, методам или полям, и выполнять действия на основе их присутствия. Например, вы можете создать код, который автоматически находит все методы с определенной аннотацией и вызывает их.

Class myClassClass = MyClass.class; Method[] methods = myClassClass.getMethods(); for (Method method : methods) < if (method.isAnnotationPresent(MyCustomAnnotation.class)) < // Вызываем метод, помеченный аннотацией MyCustomAnnotation method.invoke(instance, args); >>

Заключение

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

Завершить статью хотел бы полезной рекомендацией. Уже скоро у моих коллег из OTUS пройдет бесплатный вебинар про TCP/IP сервер в Java. Регистрация абсолютно бесплатна, поэтому рекомендую к посещению!

  • Блог компании OTUS
  • Программирование
  • Java

Рефлексия кода, reflection

Рефлексия (от reflexio — обращение назад) — это механизм исследования данных о программе во время её выполнения. Рефлексия в Java осуществляется с помощью Java Reflection API, состоящий из классов пакетов java.lang и java.lang.reflect. В информатике рефлексия означает процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения.

Java Reflection API позволяет получать информацию о конструкторах, методах и полях классов и выполнять следующие операции над полями и методами объекта/класса :

  • определение класса объекта;
  • получение информации о полях, методах, конструкторах и суперклассах;
  • получение информации о модификаторах полей и методов;
  • создание экземпляра класса, имя которого неизвестно до момента выполнения программы;
  • определение и изменение значений свойств объекта/класса;
  • вызов методов объекта/класса.

Примечание : в тексте используется объект/класс. При работе с объектом (реализацией класса) можно обращаться к полям и методам класса напрямую, если они доступны (не private). При работе с классом можно обращаться к методам класса с использованием Java Reflection API. Но класс необходимо получить из объекта.

Определение свойств класса

В работающем приложении для получения класса необходимо использовать метод forName (String className). Следующий код демонстрирует возможность создания класса без использования и с использованием Reflection :

// Без использования Reflection Foo foo = new Foo(); // С использованием Reflection Class foo = Class.forName("Foo"); // Загрузка JDBC-драйвера Class.forName("com.mysql.jdbc.Driver");

Метод класса forName(className) часто используется для загрузки JDBC-драйвера.

Методом getName() объекта Class можно получить наименование класса, включающего пакет (package) :

Class aclass = foo.getClass(); System.out.println (aclass.getName());

Для получения значения модификатора класса используется метод getModifiers(). Класс java.lang.reflect.Modifier содержит статические методы, возвращающие логическое значения проверки модификатора класса :

Class cls = foo.getClass(); int mods = cls.getModifiers(); if (Modifier.isPublic (mods)) < System.out.println("public"); >if (Modifier.isAbstract(mods)) < System.out.println("abstract");>if (Modifier.isFinal (mods))

Для получения суперкласса рефлексированного объекта (класса) необходимо использовать метод getSuperclass() :

Class cls = foo.getClass(); Class superCls = cls.getSuperClass();

Поскольку в Java отсутствует множественное наследование, то для получения всех предков следует рекурсивно вызвать метод getSuperclass() в цикле, пока не будет достигнут Object, являющийся родителем всех классов. Object не имеет родителей, поэтому вызов его метода getSuperclass() вернет null.

Определение интерфейсов и конструкторов класса

Для получения в режиме run-time списка реализующих классом интерфейсов, необходимо получить Class и использовать его метод getInterfaces(). В следующем примере извлекается список интерфейсов класса ArrayList :

Class cls = ArrayList.class; Class[] ifs = cls.getInterfaces(); System.out.println(«List of interfaces\n»); for(Class ifc : ifs)

Чтобы IDE (Eclipse) не предупреждала о необходимости определения типа класса

Class is a raw type. References to generic type Class should be parameterized

в коде были использованы generic’и. В консоль выводятся следующие интерфейсы, реализуемые классом ArrayList :

List of interfaces java.util.List java.util.RandomAccess java.lang.Cloneable java.io.Serializable

Метод класса getConstructors() позволяет получить массив открытых конструкторов типа java.lang.reflect.Constructor. После этого, можно извлекать информацию о типах параметров конструктора и генерируемых исключениях :

Class cls = obj.getClass(); Constructor[] constructors = cls.getConstructors(); for (Constructor constructor : constructors) < Class[] params = constructor.getParameterTypes(); for (Class param : params) < System.out.println(param.getName()); >>

Определение полей класса

Метод getFields() объекта Class возвращает массив открытых полей типа java.lang.reflect.Field, которые могут быть определены не только в данном классе, но также и в его родителях (суперклассе), либо интерфейсах, реализованных классом или его родителями. Класс Field позволяет получить имя поля, тип и модификаторы :

Class cls = obj.getClass(); Field[] fields = cls.getFields(); for (Field field : fields) < Classfld = field.getType(); System.out.println("Class name : " + field.getName()); System.out.println("Class type : " + fld.getName()); >

Если известно наименование поля, то можно получить о нем информацию с помощью метода getField() объекта Class.

Class cls = obj.getClass(); Field fld = cls.getField("fieldName");

Методы getField() и getFields() возвращают только открытые члены данных класса. Чтобы получить все поля класса, включая закрытые и защищенные, необходимо использовать методы getDeclaredField() и getDeclaredFields(). Данные методы работают точно так же, как и их аналоги getField() и getFields().

Определение значений полей класса

Класс Field содержит специализированные методы для получения значений примитивных типов: getInt(), getFloat(), getByte() и др. Для установки значения поля, используется метод set(). Для примитивных типов имеются методы setInt(), setFloat(), setByte() и др.

Class cls = obj.getClass(); Field field = cls.getField("fieldName"); String value = (String) field.get(obj); field.set(obj, "New value");

Ниже приведен пример изменения значения закрытого поля класса в режиме run-time.

Определение методов класса

Метод getMethods() объекта Class возвращает массив открытых методов типа java.lang.reflect.Method. Эти методы могут быть определены не только в классе, но также и в его родителях (суперклассе), либо интерфейсах, реализованных классом или его родителями. Класс Method позволяет получить имя метода, тип возвращаемого им значения, типы параметров метода, модификаторы и генерируемые исключения.

Class cls = obj.getClass(); Method[] methods = cls.getMethods(); for (Method method : methods) < System.out.println("Method name : " + method.getName()); System.out.println("Return type : " + method.getReturnType().getName()); Class[] params = method.getParameterTypes(); System.out.print("Parameters : "); for (Class paramType : params) < System.out.print(" " + paramType.getName()); >System.out.println(); >

Если известно имя метода и типы его параметров, то можно получить отдельный метод класса :

Class cls = obj.getClass(); Class[] params = new Class[] ; Method method = cls.getMethod("methodName", params);

Пример изменения значения закрытого поля класса

Чтобы изменить значение закрытого (private) поля класса необходимо получить это поле методом getDeclaredField () и вызвать метод setAccessible (true) объекта Field, чтобы открыть доступ к полю. После этого значение закрытого поля можно изменять, если оно не final. В следующем примере определен внутренний класс PrivateFinalFields с набором закрытых полей; одно из полей final. При создании объекта класса поля инициализируются. В методе main примера поочередно в закрытые поля вносятся изменения и свойства объекта выводятся в консоль.

import java.lang.reflect.Field; class PrivateFinalFields < private int i = 1; private final String s = "String S"; private String s2 = "String S2"; public String toString() < return "i = " + i + ", " + s + ", " + s2; >> public class ModifyngPrivateFields < public static void main(String[] args) throws Exception < PrivateFinalFields pf = new PrivateFinalFields(); Field f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); f.setInt(pf, 47); System.out.println("1. " + pf); f = pf.getClass().getDeclaredField("s"); f.setAccessible(true); f.set(pf, "MODIFY S"); System.out.println("2. " + pf); f = pf.getClass().getDeclaredField("s2"); f.setAccessible(true); f.set(pf, "MODIFY S2"); f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); f.setInt(pf, 35); System.out.println("3. " + pf); >>

В результате выполнения примера в консоль будут выведены следующие сообщения :

1. i = 47, String S, String S2 2. i = 47, String S, String S2 3. i = 35, String S, MODIFY S2

Из приведённого примера видно что поля private можно изменять. Для этого необходимо получить объект типа java.lang.reflect.Field с помощью метода getDeclaredField (), вызвать его метод setAccessible (true) и с помощью метода set () установить требуемое значение поля. Необходимо иметь в виду, что наличие модификатора final в закрытом текстовом поле не вызывает исключений при изменении значений, а само значение поля остаётся прежним, т.е. final поля остаются неизменные. Если не вызвать метод открытия доступа к полю setAccessible (true), то будет вызвано исключение java.lang.IllegalAccessException.

Пример вызова метода, invoke

Java Reflection Api позволяет вызвать метод класса. Рассмотрим пример, в котором определим класс Reflect, включающий поля и методы управления ими. В режиме run-time с помощью метода данного класса будем изменять значения полей и распечатывать их.

Листинг класса Reflect

Класс Reflect включает два закрытых поля (id, name) и методы управления их значениями set/get. Дополнительно в класс включим метод setData, который будем вызывать для изменения значений полей, и метод toString для печати их значений.

class Reflect < private String name; private int id; Reflect() < name = "Test"; >public int getId() < return id; >public void setId(int id) < this.id = id; >String getName() < return name; >public void setName(String name) < this.name = name; >public void setData(final int id, String name) < this.id = id; this.name = name; >@Override public String toString() < return "Reflect [ id : " + id + ", name : " + name + "]"; >>

Для тестирования объекта типа Reflect с помощью Java Reflection Api создадим класс ReflectionTest. В этот класс включим две процедуры getClassFields и getClassMethods, которые в режиме run-time распечатают всю информацию (описание полей и методов) о классе. Методы получают класс в качестве параметра. В процедурах сначала определяются массивы полей и методы; после этого их параметры распечатываются :

private void getClassFields(Class cls) < Field[] fields = cls.getDeclaredFields(); System.out.println("Class fields"); for (Field field : fields) < Classfld = field.getType(); System.out.println("Class name : " + field.getName()); System.out.println("Class type : " + fld.getName()); > > private void getClassMethods(Class cls) < Method[] methods = cls.getDeclaredMethods(); System.out.println("Class methods"); for (Method method : methods) < System.out.println("Method name : " + method.getName()); System.out.println("Return type : " + method.getReturnType().getName()); Class[] params = method.getParameterTypes(); System.out.print("Parameters : "); for (Class param : params) System.out.print(" " + param.getName()); System.out.println(); > >

В конструкторе класса ReflectionTest сначала вызываются процедуры определения полей и методов объекта/класса Reflect. После этого вызываются методы изменения значений и печати значений с использованием Reflection API. Для определения метода setData используется массив типов параметров. Вызов метода setData выполняется с передачей ему массива новых значений.

public class ReflectionTest < static Reflect reflect; public ReflectionTest() < getClassFields (reflect.getClass()); getClassMethods(reflect.getClass()); Classcls = reflect.getClass(); try < System.out.println("\n1. invoke method toString()\n"); Method method = cls.getMethod("toString"); System.out.println(method.invoke(reflect)); Class[] paramTypes; Object [] args; paramTypes = new Class[] ; method = cls.getMethod("setData", paramTypes); args = new Object[]; method.invoke(reflect, args); System.out.println("\n2. invoke method toString()\n"); method = cls.getMethod("toString"); System.out.println(method.invoke(reflect)); > catch (NoSuchMethodException e) < >catch (SecurityException e) < >catch (IllegalAccessException e) < >catch (IllegalArgumentException e) < >catch (InvocationTargetException e) < >> private void getClassFields(Class cls) < // код метода представлен выше >private void getClassMethods(Class cls) < // код метода представлен выше >public static void main(String[] args) < reflect = new Reflect(); new ReflectionTest(); System.exit(0); >>

В результате выполнения примера в консоль будут выведены представленные ниже сообщения. Методы setData и toString(), вызываемые с помощью Java Reflection API, вносят измнения в закрытые поля класса и распечатываются их значения.

Class fields Class name : name Class type : java.lang.String Class name : id Class type : int Class methods Method name : toString Return type : java.lang.String Parameters : Method name : getId Return type : int Parameters : Method name : setId Return type : void Parameters : int Method name : getName Return type : java.lang.String Parameters : Method name : setName Return type : void Parameters : java.lang.String Method name : setData Return type : void Parameters : int java.lang.String 1. invoke method toString() Reflect [ id : 999, name : Test] 2. invoke method toString() Reflect [ id : 123, name : New value]

Скачать пример

Исходный код рассмотренного примера вызова метода invoke с использованием Java Reflection API можно скачать здесь (989 байт).

Java Reflection API

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

Освойте профессию «Java-разработчик»

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

Возможности

Помимо самомодификации, API способен проводить самопроверку и самоклонирование. Чаще всего рефлексию Java используют:

  • для получения информации о классах, интерфейсах, функциях, конструкторах, методах и модулях;
  • изменения имен функций и классов во время выполнения программы;
  • создания новых экземпляров классов;
  • анализа и исполнения кода, поступающего из программного окружения;
  • преобразования классов из одного типа в другой;
  • создания массивов данных и манипуляций с ними;
  • установления значений полей объектов по именам;
  • получения доступа к переменным и методам, включая приватные, и к внешним классам;
  • вызова методов объектов по именам.

Профессия / 14 месяцев
Java-разработчик

Освойте востребованный язык

Group 1321314345 (4)

Особенности рефлексии в Java

Снижение производительности программы

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

Блокировка диспетчером безопасности

Для запуска рефлексии в программировании требуется разрешение на выполнение, которое, как правило, не выдается при работе программного компонента под управлением Security Manager (диспетчера безопасности).

Станьте Java-разработчиком
и создавайте сложные сервисы
на востребованном языке

Уязвимость

При неверном использовании API способен нарушать один из главных принципов объектно-ориентированного программирования — инкапсуляцию данных. Это может привести к появлению потенциальных уязвимостей в веб-приложениях. В период с 2013 по 2016 год в библиотеке Reflection существовала брешь, которая позволяла хакерам обходить «песочницу» (изолированную зону для выполнения программ).

Нарушение переносимости программы

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

Пример работы Reflection API в Java

Чтобы использовать Java Reflection API, не нужно подключать сторонние библиотеки. Все расположено в пакете java.lang.reflect.

Продемонстрируем некоторые методы рефлексии в программировании на конкретных примерах.

// Демонстрация работы Рефлексии в Java

import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;

// Создание объектов для класса Sample

// Создаем приватное поле private

// Создаем публичный конструктор

// Создаем публичный метод без параметров

public void method1() System.out.println(«Информация в строке — » + s); >

// Создаем публичный метод с целым числом в качестве параметра

public void method2(int x) System.out.println(«Целое число — » + x);
>

// Создаем приватный метод

private void method3() System.out.println(«Вызов приватного метода»);
>
>
class Exercise


public static void main(String args[]) throws Exception

// Создаем объект для последующей проверки свойств

Sample obj = new Sample();

// Создаем новый объект класса из другого объекта

Class cls = obj.getClass();
System.out.println(«Имя класса — » +
cls.getName());

// Получаем имя конструктора класса с помощью объекта

Constructor constructor = cls.getConstructor();
System.out.println(«Имя конструктора — » +
constructor.getName());
System.out.println(«Это публичные методы классов: «);

// Получаем методы классов с помощью объектов

Method[] methods = cls.getMethods();

// Выводим имена методов

for (Method method:methods)
System.out.println(method.getName());

// Создаем объект нужного метода с помощью имени метода и параметра класса

Method methodcall1 = cls.getDeclaredMethod(«method2», int.class);

// Вызов метода во время исполнения

// Создаем объект нужного поля с помощью имени поля

Field field = cls.getDeclaredField(«s»);

// Открываем доступ к полю независимо от используемого в нем спецификатора доступа

// Устанавливаем новое значение поля

// Создаем объект метода с помощью имени метода

Method methodcall2 = cls.getDeclaredMethod(«method1»);

// Вызов метода во время исполнения

// Создаем третий объект метода с помощью имени метода

Method methodcall3 = cls.getDeclaredMethod(«method3»);

// Изменяем настройки доступа

// Вызов метода во время исполнения

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

Java-разработчик

Java уже 20 лет в мировом топе языков программирования. На нем создают сложные финансовые сервисы, стриминги и маркетплейсы. Освойте технологии, которые нужны для backend-разработки, за 14 месяцев.

Reflection API в Java — Часть 1

В этой статье мы узнаем, что такое Рефлексия (Reflection) в Java, зачем она нам нужна, каковы её минусы, а также научимся базовой работе с ней.

Для начала нам необходимо будет разобрать немного теории.

Что такое Рефлексия?

Рефлексия — это API, который позволяет:

  • получать информацию о переменных, методах внутри класса, о самом классе, его конструкторах, реализованных интерфейсах и т.д.;
  • получать новый экземпляр класса;
  • получать доступ ко всем переменным и методам, в том числе приватным;
  • преобразовывать классы одного типа в другой (cast);
  • делать все это во время исполнения программы (динамически, в Runtime).

Минусы Рефлексии

Как и у всего в этом мире, у Рефлексии есть свои недостатки:

  • Худшая производительность в сравнении с классической работой с классами, методами и переменными;
  • Ограничения безопасности. Если мы захотим использовать рефлексию на классе, который защищен с помощью специального класса SecurityManager, то ничего у не выйдет т.к. этот класс будет выбрасывать исключения каждый раз, как мы попытаемся получить доступ к закрытым членам класса. Такая защита может применяться, например, в Апплетах (Applets);
  • Получение доступа к внутренностям класса, что нарушает принцип инкапсуляции. Фактически, мы получаем доступ туда, куда обычному человеку лезть не желательно. Это как с розеткой, ребёнку лучше к ней не лезть, тогда как опытный электрик запросто с ней поладит.

Что такое Класс класса, у кого он есть?

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

Class есть у :

  • классов, интерфейсов, перечислений;
  • примитивов и обёрток над ними;
  • массивов;
  • void. Да, ключевое слово void также имеет Class.

В общем, Class есть у всех объектов в Java.

А теперь перейдем к практике, для этого нам понадобится класс Car

package com . vertex . reflection ;
private int horsepower ;
public final String serialNumber ;
public Car ( ) < serialNumber = "" ; public Car ( int horsepower , String serialNumber ) < this . horsepower = horsepower ; this . serialNumber = serialNumber ; public int getHorsepower ( ) < return horsepower ; void setHorsepower ( int horsepower ) < this . horsepower = horsepower ; protected void printSerialNumber ( ) < System . out . println ( serialNumber ) ;

Как получить Класс класса?

Способ 1 — Сlass.forName(“имя.пакета.ИмяКласса”)

Class < ? >carClass = Class . forName ( «com.vertex.reflection.Car» ) ;
> catch ( ClassNotFoundException e ) < e . printStackTrace ( ) ;

Вызов метода forName() необходимо обернуть в блок try-catch т.к. метод может бросить ClassNotFoundException, в случае если он не найдет класс с таким именем.

Способ 2 — метод getClass() у экземпляра класса

Car car = new Car ( ) ;
Class < ? extends Car >carClass = car . getClass ( ) ;

В этом случае оборачивать метод getClass() в блок try-catch нет необходимости т.к. мы вызываем этот метод у существующего класса, который видит компилятор. Но, к сожалению, компилятор не может знать тип переменной до конца, поэтому мы и имеем «? extends Car», как дженерик тип.

Способ 3 — ИмяКласса.class

Class < Car >carClass = Car . class ;

Здесь по той же причине не нужно использовать блок try-catch.

Если вы заметили, то чем больше мы знаем о классе, тем точнее будет тип класса при получении Класса класса.

В первом случае мы знаем только относительный путь к классу, это самый ненадёжный способ, поэтому и тип дженерика был Class. Ведь тип будет известен только после того, как мы запустим программу, компилятор этого знать не может наперёд.

Во втором случае мы получили Класс класса прямиком из экземпляра класса. Такой способ надёжнее, но он является не самым оптимальным. В этом случае компилятор не знает, экземпляр этого класса перед нами или его наследник, поэтому он подставляет тип Class.

В третьем случае мы прямо указываем из какого класса мы хотим получить Класс. Тут нет возможности ошибится в имени, нет возможности получить класс наследника класса Car, как это было возможно во втором случае. Компилятор точно знает, что это за тип класса. Этот способ является самым надёжным, поэтому и тип здесь Class.

Как получить информацию о переменных класса с помощью Рефлексии?

Получить информацию о переменных класса можно с помощью методов getDeclaredFields(), getDeclaredField() и getFields(), getField().

Пример 1 getDeclaredFields()

Метод возвращает все объявленные переменные в классе

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *