Поиск по сайту:

Учебник по примеру Java Reflection


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

Java-отражение

  1. Отражение в Java
  2. Отражение Java для классов
    • Получить объект класса
    • Получить суперкласс
    • Получить общедоступные классы членов
    • Получить объявленные классы
    • Получить объявление класса
    • Получение имени пакета
    • Получение модификаторов класса
    • Получить параметры типа
    • Получить реализованные интерфейсы
    • Получить все общедоступные методы
    • Получить все общедоступные конструкторы
    • Получить все общедоступные поля
    • Получить все аннотации
  3. Отражение Java для полей
    • Получить общедоступное поле
    • Поле, объявляющее класс
    • Получить тип поля
    • Получить/установить значение открытого поля
    • Получить/установить значение частного поля
  4. Отражение Java для методов
    • Получить общедоступный метод
    • Вызов общедоступного метода
    • Вызов приватных методов
  5. Отражение Java для конструкторов
    • Получить общедоступный конструктор
    • Создать экземпляр объекта с помощью конструктора
  6. Java Reflection для аннотаций

Отражение в Java

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

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

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

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

Отражение Java для классов

В Java каждый объект является либо примитивным типом, либо ссылкой. Все классы, перечисления и массивы являются ссылочными типами и наследуются от java.lang.Object. Примитивные типы: boolean, byte, short, int, long, char, float и double. 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");
	}
	
	// inner public class
	public class BaseClassInnerClass{}
		
	//member 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;
	}
	
	// inner classes
	public class ConcreteClassPublicClass{}
	private class ConcreteClassPrivateClass{}
	protected class ConcreteClassProtectedClass{}
	class ConcreteClassDefaultClass{}
	
	//member enum
	enum ConcreteClassDefaultEnum{}
	public enum ConcreteClassPublicEnum{}
	
	//member interface
	public interface ConcreteClassPublicInterface{}

}

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

Получить объект класса

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

// Get Class using reflection
Class<?> concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
	// below method is used most of the times in frameworks like JUnit
	//Spring dependency injection, Tomcat web container
	//Eclipse auto completion of method names, hibernate, Struts2 etc.
	//because ConcreteClass is not available at compile time
	concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass

//for primitive types, wrapper classes and arrays
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 использует Generics, это помогает фреймворкам убедиться, что полученный класс является подклассом базового класса фреймворка. Ознакомьтесь с учебным пособием по Java Generics Tutorial, чтобы узнать об обобщениях и их подстановочных знаках.

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

Метод getSuperclass() объекта класса возвращает суперкласс класса. Если этот класс представляет либо класс объекта, интерфейс, примитивный тип или пустоту, то возвращается значение 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() класса, представляющего объект, возвращает массив, содержащий объекты класса, представляющие все общедоступные классы, интерфейсы и перечисления, которые являются членами класса, представленного этим объектом класса. Сюда входят члены открытого класса и интерфейса, унаследованные от суперклассов, а также члены открытого класса и интерфейса, объявленные классом. Этот метод возвращает массив длины 0, если этот объект класса не имеет общедоступных классов-членов или интерфейсов или если этот объект класса представляет примитивный тип, класс массива или пустоту.

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() возвращает массив объектов класса, отражающих все классы и интерфейсы, объявленные как члены класса, представленного этим объектом класса. Возвращаемый массив не включает классы, объявленные в унаследованных классах и интерфейсах.

//getting all of the classes, interfaces, and enums that are explicitly declared in ConcreteClass
Class<?>[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//prints [class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultEnum, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPrivateClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassProtectedClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));

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

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

Class<?> innerClass = Class.forName("com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass");
//prints com.journaldev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());

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

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

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

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

Метод getModifiers() возвращает int-представление модификаторов класса, мы можем использовать метод 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, если с классом связаны какие-либо параметры типа. Параметры типа возвращаются в том же порядке, в котором они объявлены.

//Get Type parameters (generics)
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();
//prints "[java.util.Map<K, V>, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//prints "[interface java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));		

Получить все общедоступные методы

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

Method[] publicMethods = Class.forName("com.journaldev.reflection.ConcreteClass").getMethods();
//prints public methods of ConcreteClass, BaseClass, Object
System.out.println(Arrays.toString(publicMethods));

Получить все общедоступные конструкторы

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

//Get All public constructors
Constructor<?>[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//prints public constructors of ConcreteClass
System.out.println(Arrays.toString(publicConstructors));

Получить все общедоступные поля

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

//Get All public fields
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//prints public fields of ConcreteClass, it's superclass and super interfaces
System.out.println(Arrays.toString(publicFields));

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

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

java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//prints [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));

Отражение Java для полей

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

Получить публичное поле

В последнем разделе мы увидели, как получить список всех общедоступных полей класса. API Reflection также предоставляет метод для получения определенного общедоступного поля класса с помощью метода 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() возвращает объект класса для объявленного типа поля, если поле является примитивным типом, он возвращает объект класса-оболочки.

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() возвращает объект, поэтому, если поле имеет примитивный тип, он возвращает соответствующий класс-оболочку. Если поле статическое, мы можем передать Object как null в методе get(). Существует несколько методов set*() для установки Object в поле или установки различных типов примитивных типов в поле. Мы можем получить тип поля, а затем вызвать правильную функцию, чтобы правильно установить значение поля. Если поле является окончательным, методы set() вызывают исключение java.lang.IllegalAccessException.

Получить/установить значение частного поля

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

Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//turning off access check with below method call
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"

Отражение Java для методов

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

Получить общедоступный метод

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

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//get method parameter types, prints "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
//get method return type, return "class java.lang.Object", class reference for void
System.out.println(method.getReturnType());
//get method modifiers
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() базового класса, который является статическим и не имеет параметров.

//invoking private method
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"

Java Reflection для конструкторов

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

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

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

Constructor<?> constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
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);
//getting constructor parameters
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<String,String> myMap = (HashMap<String,String>) hashMapConstructor.newInstance(null);

Отражение для аннотаций

Аннотации были введены в Java 1.5 для предоставления метаданных о классе, методах или полях, и теперь они широко используются в таких средах, как Spring и Hibernate. API Reflection также был расширен, чтобы обеспечить поддержку анализа аннотаций во время выполнения. Используя API отражения, мы можем анализировать аннотации, политика хранения которых — Runtime. Я уже написал подробный учебник по аннотациям и тому, как мы можем использовать API отражения для анализа аннотаций, поэтому я предлагаю вам ознакомиться с учебником по аннотациям Java. Это все, что касается учебника по Java Reflection, я надеюсь, вам понравился учебник и вы поняли важность Java Reflection API.