Пример обучения по рефлексии в Java

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

Java Reflection

  1. Отражение в Java
  2. Отражение Java для классов
  3. Java Reflection для полей
  4. Java Reflection для методов
  5. Java Reflection для конструкторов
  6. Java Reflection для аннотаций

  1. Отражение в Java

Отражение в Java – очень мощное понятие, и оно мало пригодно в обычном программировании, но является основой для большинства фреймворков Java, J2EE. Некоторые из фреймворков, использующих отражение в Java, включают:

  1. JUnit – использует отражение для разбора аннотации @Test, чтобы получить тестовые методы и затем вызвать их.
  2. Spring – внедрение зависимостей, подробнее см. в Внедрение зависимостей в Spring
  3. Tomcat – веб-контейнер, использующий отражение для перенаправления запроса к правильному модулю, разбирая их файлы web.xml и URI запроса.
  4. Eclipse – автоматическое завершение имен методов
  5. Struts
  6. Hibernate

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

  • Плохая производительность – Поскольку отражение Java разрешает типы динамически, оно включает в себя обработку, такую ​​как сканирование пути к классу для поиска класса для загрузки, что замедляет производительность.
  • Ограничения безопасности – Отражение требует разрешений времени выполнения, которые могут быть недоступны для системы, работающей под управлением менеджера безопасности. Это может привести к сбоям вашего приложения во время выполнения из-за менеджера безопасности.
  • Проблемы безопасности – Используя отражение, мы можем получить доступ к части кода, к которому мы не должны иметь доступ, например, мы можем получить доступ к закрытым полям класса и изменить его значение. Это может быть серьезной угрозой безопасности и привести к неадекватному поведению вашего приложения.
  • Трудность в обслуживании – Код отражения трудно понять и отлаживать, также любые проблемы с кодом не могут быть обнаружены на этапе компиляции, потому что классы могут быть недоступны, что делает его менее гибким и трудным в обслуживании.
  1. Отражение Java для классов

В Java каждый объект является либо примитивным типом, либо ссылкой. Все классы, перечисления, массивы являются ссылочными типами и наследуются от java.lang.Object. Примитивные типы – boolean, byte, short, int, long, char, float и double. java.lang.Class является точкой входа для всех операций отражения. Для каждого типа объекта JVM создает неизменяемый экземпляр java.lang.Class, который предоставляет методы для изучения свойств объекта во время выполнения и создания новых объектов, вызова его методов и получения/установки полей объекта. В этом разделе мы рассмотрим важные методы класса. Для удобства я создаю некоторые классы и интерфейсы с иерархией наследования.

package com.journaldev.reflection;

public interface BaseInterface {
	
	public int interfaceInt=0;
	
	void method1();
	
	int method2(String str);
}
package com.journaldev.reflection;

public class BaseClass {

	public int baseInt;
	
	private static void method3(){
		System.out.println("Method3");
	}
	
	public int method4(){
		System.out.println("Method4");
		return 0;
	}
	
	public static int method5(){
		System.out.println("Method5");
		return 0;
	}
	
	void method6(){
		System.out.println("Method6");
	}
	
	// внутренний публичный класс
	public class BaseClassInnerClass{}
		
	// член перечисления public enum
	public enum BaseClassMemberEnum{}
}
package com.journaldev.reflection;

@Deprecated
public class ConcreteClass extends BaseClass implements BaseInterface {

	public int publicInt;
	private String privateString="private string";
	protected boolean protectedBoolean;
	Object defaultObject;
	
	public ConcreteClass(int i){
		this.publicInt=i;
	}

	@Override
	public void method1() {
		System.out.println("Method1 impl.");
	}

	@Override
	public int method2(String str) {
		System.out.println("Method2 impl.");
		return 0;
	}
	
	@Override
	public int method4(){
		System.out.println("Method4 overriden.");
		return 0;
	}
	
	public int method5(int i){
		System.out.println("Method4 overriden.");
		return 0;
	}
	
	// внутренние классы
	public class ConcreteClassPublicClass{}
	private class ConcreteClassPrivateClass{}
	protected class ConcreteClassProtectedClass{}
	class ConcreteClassDefaultClass{}
	
	// член интерфейса public enum
	enum ConcreteClassDefaultEnum{}
	public enum ConcreteClassPublicEnum{}
	
	// член интерфейса
	public interface ConcreteClassPublicInterface{}

}

Давайте рассмотрим некоторые из важных методов отражения для классов.

Получение объекта Class

Мы можем получить Class объекта с использованием трех методов – через статическую переменную class, с использованием метода getClass() объекта и java.lang.Class.forName(String fullyClassifiedClassName). Для примитивных типов и массивов мы можем использовать статическую переменную class. Оберточные классы предоставляют еще одну статическую переменную TYPE для получения класса.

// Получение Class с использованием отражения
Class concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
	// нижеприведенный метод используется большинством фреймворков, таких как JUnit
	//внедрение зависимости Spring, веб-контейнер Tomcat
	//автозавершение методов Eclipse, Hibernate, Struts2 и др.
	//потому что ConcreteClass не доступен на этапе компиляции
	concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass

//для примитивных типов, оберточных классов и массивов
Class booleanClass = boolean.class;
System.out.println(booleanClass.getCanonicalName()); // prints boolean

Class cDouble = Double.TYPE;
System.out.println(cDouble.getCanonicalName()); // prints double

Class cDoubleArray = Class.forName("[D");
System.out.println(cDoubleArray.getCanonicalName()); //prints double[]

Class twoDStringArray = String[][].class;
System.out.println(twoDStringArray.getCanonicalName()); // prints java.lang.String[][]

getCanonicalName() возвращает каноническое имя базового класса. Обратите внимание, что java.lang.Class использует обобщения, что помогает фреймворкам гарантировать, что полученный класс является подклассом базового класса фреймворка. Посмотрите Руководство по обобщениям в Java, чтобы узнать о обобщениях и их подстановочных знаках.

Получить суперкласс

Метод getSuperclass() на объекте Class возвращает суперкласс класса. Если этот Class представляет собой либо класс Object, интерфейс, примитивный тип или void, то возвращается null. Если этот объект представляет собой массивовый класс, то возвращается объект Class, представляющий класс Object.

Class<?> superClass = Class.forName("com.journaldev.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); // prints "class com.journaldev.reflection.BaseClass"
System.out.println(Object.class.getSuperclass()); // prints "null"
System.out.println(String[][].class.getSuperclass());// prints "class java.lang.Object"

Получить открытые вложенные классы

Метод getClasses() представления объекта Class возвращает массив, содержащий объекты Class, представляющие все публичные классы, интерфейсы и перечисления, которые являются членами класса, представленного этим объектом Class. Это включает в себя публичные классы и интерфейсы, унаследованные от суперклассов, а также публичные классы и интерфейсы, объявленные классом. Этот метод возвращает массив длиной 0, если у этого объекта Class нет публичных классов или интерфейсов, или если этот объект Class представляет примитивный тип, класс массива или void.

Class[] classes = concreteClass.getClasses();
//[class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface,
//class com.journaldev.reflection.BaseClass$BaseClassInnerClass, 
//class com.journaldev.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));

Получить Объявленные Классы

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

//получение всех классов, интерфейсов и перечислений, явно объявленных в ConcreteClass
Class[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//печатает [класс com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass, 
//класс com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultEnum, 
//класс com.journaldev.reflection.ConcreteClass$ConcreteClassPrivateClass, 
//класс com.journaldev.reflection.ConcreteClass$ConcreteClassProtectedClass, 
//класс com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//класс com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//интерфейс com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));

Получить объявляющий класс

getDeclaringClass() метод возвращает объект Class, представляющий класс, в котором он был объявлен.

Class innerClass = Class.forName("com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass");
//печатает com.journaldev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());

Получение имени пакета

getPackage() метод возвращает пакет для этого класса. Загрузчик классов этого класса используется для поиска пакета. Мы можем вызвать метод getName() Package, чтобы получить имя пакета.

//prints "com.journaldev.reflection"
System.out.println(Class.forName("com.journaldev.reflection.BaseInterface").getPackage().getName());

Получение модификаторов класса

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

System.out.println(Modifier.toString(concreteClass.getModifiers())); //prints "public"
//prints "public abstract interface"
System.out.println(Modifier.toString(Class.forName("com.journaldev.reflection.BaseInterface").getModifiers())); 

Получение типовых параметров

getTypeParameters() возвращает массив объектов TypeVariable, если существуют типовые параметры, связанные с классом. Типовые параметры возвращаются в том же порядке, как они объявлены.

//Получение типовых параметров (обобщений)
TypeVariable[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();
for(TypeVariable t : typeParameters)
System.out.print(t.getName()+",");

Получение реализованных интерфейсов

getGenericInterfaces() метод возвращает массив интерфейсов, реализованных классом с информацией о типах. Мы также можем использовать метод getInterfaces(), чтобы получить классовое представление всех реализованных интерфейсов.

Type[] interfaces = Class.forName("java.util.HashMap").getGenericInterfaces();
//печатает "[java.util.Map, интерфейс java.lang.Cloneable, интерфейс java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//печатает "[интерфейс java.util.Map, интерфейс java.lang.Cloneable, интерфейс java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));		

Получить все открытые методы

getMethods() метод возвращает массив открытых методов класса, включая открытые методы его суперклассов и суперинтерфейсов.

Method[] publicMethods = Class.forName("com.journaldev.reflection.ConcreteClass").getMethods();
//печатает открытые методы ConcreteClass, BaseClass, Object
System.out.println(Arrays.toString(publicMethods));

Получить все открытые конструкторы

getConstructors() метод возвращает список открытых конструкторов ссылки на класс объекта.

//Получить все открытые конструкторы
Constructor[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//печатает открытые конструкторы ConcreteClass
System.out.println(Arrays.toString(publicConstructors));

Получить все открытые поля

getFields() метод возвращает массив открытых полей класса, включая открытые поля его суперклассов и суперинтерфейсов.

//Получить все открытые поля
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//печатает открытые поля ConcreteClass, его суперкласса и суперинтерфейсов
System.out.println(Arrays.toString(publicFields));

Получить все аннотации

getAnnotations() метод возвращает все аннотации для элемента, мы можем использовать его с классами, полями и методами. Обратите внимание, что только аннотации, доступные с помощью отражения, имеют политику удержания RUNTIME, ознакомьтесь с Руководство по аннотациям в Java. Мы рассмотрим это более подробно в последующих разделах.

java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//печатает [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));
  1. Java Reflection для полей

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

Получить открытое поле

В последнем разделе мы видели, как получить список всех открытых полей класса. API рефлексии также предоставляет метод для получения конкретного открытого поля класса через метод getField(). Этот метод ищет поле в указанной ссылке на класс, затем в суперинтерфейсах и затем в суперклассах.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");

Вышеуказанный вызов вернет поле из BaseInterface, которое реализуется ConcreteClass. Если поле не найдено, то будет сгенерировано исключение NoSuchFieldException.

Класс объявления поля

Мы можем использовать метод getDeclaringClass() объекта поля, чтобы получить класс, объявляющий поле.

try {
	Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
	Class<?> fieldClass = field.getDeclaringClass();
	System.out.println(fieldClass.getCanonicalName()); //prints com.journaldev.reflection.BaseInterface
} catch (NoSuchFieldException | SecurityException e) {
	e.printStackTrace();
}

Получить тип поля

Метод getType() возвращает объект Class для объявленного типа поля; если поле является примитивным типом, то возвращается объект класса-оболочки.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
Class<?> fieldType = field.getType();
System.out.println(fieldType.getCanonicalName()); //prints int			

Получение/Установка Значения Публичного Поля

Мы можем получать и устанавливать значение поля в объекте с использованием рефлексии.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
ConcreteClass obj = new ConcreteClass(5);
System.out.println(field.get(obj)); //prints 5
field.setInt(obj, 10); //setting field value to 10 in object
System.out.println(field.get(obj)); //prints 10

Метод get() возвращает объект, поэтому, если поле является примитивным типом, он возвращает соответствующий Оберточный Класс. Если поле статическое, мы можем передать объект как null в метод get(). Существует несколько методов set*(), чтобы установить объект в поле или установить различные типы примитивов в поле. Мы можем получить тип поля, а затем вызвать правильную функцию для корректной установки значения поля. Если поле является финальным, методы set() выбрасывают исключение java.lang.IllegalAccessException.

Получение/Установка Значения Приватного Поля

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

Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//отключение проверки доступа с помощью вызова метода ниже
privateField.setAccessible(true);
ConcreteClass objTest = new ConcreteClass(1);
System.out.println(privateField.get(objTest)); // prints "private string"
privateField.set(objTest, "private string updated");
System.out.println(privateField.get(objTest)); //prints "private string updated"
  1. Отражение Java для методов

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

Получение публичного метода

Мы можем использовать метод getMethod(), чтобы получить публичный метод класса, мы должны передать имя метода и типы параметров метода. Если метод не найден в классе, API отражения ищет метод в суперклассе. В приведенном ниже примере я получаю метод put() из HashMap с использованием отражения. Пример также показывает, как получить типы параметров метода, модификаторы метода и тип возвращаемого значения метода.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//получить типы параметров метода, выводит "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
//получить тип возвращаемого значения метода, возвращает "class java.lang.Object", ссылка на класс для void
System.out.println(method.getReturnType());
//получить модификаторы метода
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"

Вызов публичного метода

Мы можем использовать метод invoke() объекта Method для вызова метода. В приведенном ниже примере кода я вызываю метод put на HashMap с использованием рефлексии.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
Map<String, String> hm = new HashMap<>();
method.invoke(hm, "key", "value");
System.out.println(hm); // prints {key=value}

Если метод статический, мы можем передать NULL в качестве аргумента объекта.

Вызов частных методов

Мы можем использовать getDeclaredMethod(), чтобы получить доступ к частному методу, а затем отключить проверку доступа для его вызова. Ниже приведен пример того, как мы можем вызвать метод3() класса BaseClass, который является статическим и не имеет параметров.

// вызов частного метода
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
  1. Java Reflection для конструкторов

Reflection API предоставляет методы для получения конструкторов класса для анализа, и мы можем создавать новые экземпляры класса, вызывая конструктор. Мы уже узнали, как получить все открытые конструкторы.

Получить открытый конструктор

Мы можем использовать метод getConstructor() на представлении класса объекта, чтобы получить конкретный открытый конструктор. Приведенный ниже пример показывает, как получить конструктор ConcreteClass, определенный выше, и конструктор без аргументов для HashMap. Также показано, как получить массив типов параметров для конструктора.

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//получение параметров конструктора
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
		
Constructor hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"

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

Мы можем использовать метод newInstance() на объекте конструктора для создания нового экземпляра класса. Поскольку мы используем рефлексию, когда у нас нет информации о классах на этапе компиляции, мы можем присвоить ее объекту, а затем далее использовать рефлексию для доступа к его полям и вызова его методов.

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//получение параметров конструктора
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
		
Object myObj = constructor.newInstance(10);
Method myObjMethod = myObj.getClass().getMethod("method1", null);
myObjMethod.invoke(myObj, null); //prints "Method1 impl."

Constructor hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"
HashMap myMap = (HashMap) hashMapConstructor.newInstance(null);
  1. Отражение для аннотаций

Аннотации были введены в Java 1.5 для предоставления метаданных о классе, методах или полях, и сейчас они широко используются в фреймворках, таких как Spring и Hibernate. API отражения также был расширен для поддержки анализа аннотаций во время выполнения. С использованием API отражения мы можем анализировать аннотации, политика удержания которых – Runtime. Я уже написал подробное руководство по аннотациям и тому, как мы можем использовать API отражения для разбора аннотаций, поэтому я бы порекомендовал вам ознакомиться с Руководством по аннотациям Java. Вот и все для примера учебника по отражению Java, надеюсь, вам понравился учебник и вы поняли важность API отражения Java.

Source:
https://www.digitalocean.com/community/tutorials/java-reflection-example-tutorial