Найти в Дзене
JavAKnazzz

Reflection API. Рефлексия.

Оглавление

Reflection API. Рефлексия. Темная сторона Java

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

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

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

Вот основной список того, что позволяет рефлексия:

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

Пример

public class MyClass {
private int number;
private String name = "default"; --- > нет геттера
// public MyClass(int number, String name) {
// this.number = number;
// this.name = name;
// }
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public void setName(String name) {
this.name = name;
}
private void printData(){ --- > нет геттера
System.out.println(number + name);
}
}

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

public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; //no getter =(
System.out.println(number + name);//output 0null
try {
Field field = myClass.getClass().getDeclaredField("name");
field.setAccessible(true);
name = (String) field.get(myClass);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(number + name);//output 0default
}

В java есть класс Class ---> он представляет классы и интерфейсы в исполняемом приложении Java.

Чтобы получить поля этого класса нужно вызвать метод getFields(), этот метод вернет нам все доступные поля класса. Нам это не подходит, так как наше поле private, поэтому используем метод getDeclaredFields(), этот метод также возвращает массив полей класса, но теперь и private и protected. В нашей ситуации мы знаем имя поля, которое нас интересует, и можем использовать метод getDeclaredField(String), где String — имя нужного поля.

Отлично, мы получили объект Field с ссылкой на наш name. Т.к. поле не было публичным (public) следует дать доступ для работы с ним. Метод setAccessible(true) разрешает нам дальнейшую работу. Теперь поле name полностью под нашим контролем! Получить его значение можно вызовом get(Object) у объекта Field, где Object — экземпляр нашего класса MyClass. Приводим к типу String и присваиваем нашей переменной name.

На тот случай если у нас вдруг не оказалось setter’a, для установки нового значения полю name можно использовать метод set:

field.set(myClass, (String) "new value");

Как получить подступ к private методу ????

public static void printData(Object myClass){
try {
Method method = myClass.getClass().getDeclaredMethod("printData");
method.setAccessible(true);
method.invoke(myClass);

} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}

Итог:

public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; //?
printData(myClass); // outout 0default
try {
Field field = myClass.getClass().getDeclaredField("name");
field.setAccessible(true);
field.set(myClass, (String) "new value");
name = (String) field.get(myClass);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
printData(myClass);// output 0new value
}

создавать экземпляры класса в режиме runtime (во время выполнения программы):

public static void main(String[] args) {
MyClass myClass = null;
try {
Class clazz = Class.forName(MyClass.class.getName());
myClass = (MyClass) clazz.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}

На момент старта java приложения далеко не все классы оказываются загруженными в JVM. Если в вашем коде нет обращения к классу MyClass, то тот, кто отвечает за загрузку классов в JVM, а им является ClassLoader, никогда его туда и не загрузит. Поэтому нужно заставить ClassLoader загрузить его и получить описание нашего класса в виде переменной типа Class. Для этой задачи существует метод forName(String), где String — имя класса, описание которого нам требуется. Получив Сlass, вызов метода newInstance() вернет Object, который будет создан по тому самому описанию. Остается привести этот объект к нашему классу MyClass.

К сожалению описанный способ будет работать только с конструктором по умолчанию (без параметров).

Как же вызывать методы с аргументами и конструкторы с параметрами?

Как и ожидалось, newInstance() не находит конструктор по умолчанию и больше не работает. Перепишем создание экземпляра класса:

public static void main(String[] args) {
MyClass myClass = null;
try {
Class clazz = Class.forName(MyClass.class.getName());
Class[] params = {int.class, String.class};
myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}

Для получения конструкторов класса следует у описания класса вызвать метод getConstructors(), а для получения параметров конструктора - getParameterTypes():

Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
Class[] paramTypes = constructor.getParameterTypes();
for (Class paramType : paramTypes) {
System.out.print(paramType.getName() + " ");
}
System.out.println();
}