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

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

  • автор:

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

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

very junior java developer Уровень 51
21 апреля 2023

Грамотно написанный класс в Java: — У меня всё как надо, всё что нужно скрыть, помечено Private, я вообще не вызываю багов вовремя работы программы. ������ Reflection API: — �� Я вас умоляю, не смешите мои подковы методы).

Griboed Уровень 30
21 февраля 2023

На собеседовании : — Reflection API, для чего он нужен? — Он позволяет наиболее циничный способом попрать принцип инкапсуляции в java.

Алексей Куренков Уровень 29
30 января 2023
proxylunae Уровень 45
22 июля 2022

Вызов метода мяу обычным способом : Кот, мяукни! Вызов метода мяу через рефлексию : Мяу, прозвучи от того кота!

SoSed Уровень 51 Expert
11 апреля 2022
Понял всё, кроме одного. Почему

 clazz = Class.forName(Cat.class.getName()); 
 clazz = Cat.class; 

Вместо того, чтобы сразу получить объект класса, мы сначала получаем имя класса (которое нам известно), а потом по нему получаем объект класса. Или это как-то связано с полным именем (learn.javarush.Cat) ?

Vadim Makarenko Уровень 28
4 ноября 2021

Я статью только начал читать, но уже появился вопрос, причём, видимо, элементарный: если класс Cat наследует класс Animal, то зачем в наследнике повторять те же поля private String name; private int age; Они же ему переходят по наследству? Вопрос второй: как понять эти строки Class clazz = Class.forName(className); Cat cat = (Cat) clazz.newInstance(); Если стоит задача создать объект класса, чьё имя на момент компиляции неизвестно, то откуда в тексте программы это самое неизвестное имя? Я бы мог представить такую строку: Object obj = clazz.newInstance(); Она была бы универсальной для любого типа. И она есть в этом же коде ниже, в методе createObject(), но он же не вызывается вроде. Вызывается createCat()

Kamoliddin Уровень 41
22 октября 2021

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

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

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

Давай соберем все возможности использования рефлексии в один список:

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

Рефлексия используется практически во всех современных технологиях Java и лежит в основе большинства современных Java/Java EE фреймворков и библиотек, например, в:

  • Spring — фреймворков для создания веб-приложений
  • JUnit — фреймворк для тестирования

Если разрабтчик никогда не сталкивался с такого рода механизмами, то у него скорее всего возникнет закономерный вопрос — а зачем все это надо? Ответ в этом случае достаточно простой, но в то же время очень неопределенный: благодаря рефлексии мы кардинально повышаем гибкость и возможность настраивать приложение под себя и под свой код.

Но всегда есть свои плюсы и минусы. Поэтому немного о минусах:

  • Нарушения безопасности приложения. С помощью рефлексии мы можем получить доступ к части кода, к которой не должны были (нарушение инкапсуляции).
  • Ограничения системы безопасности. Рефлексия требует разрешения времени выполнения, недоступные для систем под управлением менеджера безопасности.
  • Низкая производительность. Рефлексия в Java определяет типы динамически, сканируя classpath , чтобы найти класс для загрузки. Это снижает производительность программы.
  • Сложность в поддержке. Код, написанный с помощью рефлексии, трудно читать и отлаживать. Он становится менее гибким и его сложнее поддерживать.

Работа с классами с помощью Reflection API

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

Давай посмотрим на список основных методов для работы с java.lang.Class :

Метод Дейстиве
String getName(); Возвращает название класса
int getModifiers(); Возвращает модификаторы доступа
Package getPackage(); Возвращает информацию о пакете
Class getSuperclass(); Возвращает информацию о классе-родителе
Class[] getInterfaces(); Возвращает массив интерфейсов
Constructor[] getConstructors(); Возвращает информацию о конструкторах класса
Fields[] getFields(); Возвращает поля класса
Files getFiled(String fieldName); Возвращает определенное поле класса по имени
Method[] getMethods(); Возвращает массив методов

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

Сейчас мы поговорим о получении самого java.lang.Class . Для это у нас есть три способа.

1. С помощью Class.forName

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

Этот код демонстрирует возможность создавать классы с использованием Reflection. Создадим класс Person , с которым будем работать:

 package com.company; public class Person < private int age; private String name; public int getAge() < return age; >public void setAge(int age) < this.age = age; >public String getName() < return name; >public void setName(String name) < this.name = name; >>

И вторая часть нашего примера — это код с рефлексией:

 public class TestReflection < public static void main(String[] args) < try < ClassaClass = Class.forName("com.company.Person"); > catch (ClassNotFoundException e) < e.printStackTrace(); >> > 

Такой подход возможен в случае, если известно полное имя класса. Тогда можно получить соответствующий класс с помощью статического метода Class.forName() . Этот способ нельзя использовать для примитивных типов.

2. С помощью .class

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

 Class aClass = Person.class; 
3. C помощью .getClass()

Если экземпляр объекта доступен, то самый простой способ получить его класс — вызвать object.getClass() .

 Person person = new Person(); Class aClass = person.getClass(); 

В чем же отличие между двумя последними подходами?

Используй A.class , если заранее, при написании кода знаешь, какой объект класса тебя интересует. Если экземпляра нет, тогда нужно использовать .class .

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

Рассмотрим методы, которые возвращают методы нашего класса: getDeclaredMethods() и getMethods() .

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

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

Давай рассмотрим работу каждого из них.

Начнем с getDeclaredMethods() . Ниже мы будем работать с абстрактным классом Numbers , который поможет нам еще раз понять разницу между двумя методами. Напишем статический метод, который будет преобразовывать наш массив Method в List :

 import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class TestReflection < public static void main(String[] args) < final Method[] declaredMethods = Number.class.getDeclaredMethods(); ListactualMethodNames = getMethodNames(declaredMethods); actualMethodNames.forEach(System.out::println); > private static List getMethodNames(Method[] methods) < return Arrays.stream(methods) .map(Method::getName) .collect(Collectors.toList()); >> 

Результат работы кода выглядит вот так:

byteValue
shortValue
intValue
longValue
floatValue
doubleValue

Это и есть методы, которые объявлены внутри класса Number . А что вернет нам getMethods() ? Изменим две строки в примере:

 final Method[] methods = Number.class.getMethods(); List actualMethodNames = getMethodNames(methods); 

И в результате мы увидим вот такой набор методов:

byteValue
shortValue
intValue
longValue
floatValue
doubleValue
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

Так как все классы наследуются от Object , наш метод вернул нам еще и публичные методы класса Object .

Получение полей класса

Методы getFields и getDeclaredFields используются для получения полей класса. Посмотрим на примере класса LocalDateTime . Перепишем наш код:

 import java.lang.reflect.Field; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class TestReflection < public static void main(String[] args) < final Field[] declaredFields = LocalDateTime.class.getDeclaredFields(); ListactualFieldNames = getFieldNames(declaredFields); actualFieldNames.forEach(System.out::println); > private static List getFieldNames(Field[] fields) < return Arrays.stream(fields) .map(Field::getName) .collect(Collectors.toList()); >> 

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

MIN
MAX
serialVersionUID
date
time

По аналогии с методами посмотрим, что будет, если немного изменить код:

 final Field[] fields = LocalDateTime.class.getFields(); List actualFieldNames = getFieldNames(fields); 

Теперь давай разберем, в чем разница между нашими методами.

Метод getDeclaredFields возвращает массив объектов Field , отражающих все поля, объявленные классом или интерфейсом, представленным этим объектом Class .

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

А теперь заглянем внутрь нашего LocalDateTime .

Поля класса MIN и MAX являются общедоступными, значит, они будут видны через метод getFields . В свою очередь поля date , time , serialVersionUID имеют модификатор private , а значит не будут видны через метод getFields , и получить их мы можем с помощью getDeclaredFields . Таким образом мы можем получить доступ к полям (Field) для private полей.

Другие методы и их описание

Настало время поговорить о некоторых методы нашего класса Class , а именно:

Метод Действие
getModifiers Получение модификаторов нашего класса
getPackage Получение пакета, в котором лежит наш класс
getSuperclass Получение суперкласса
getInterfaces Получение массива интерфейсов, которые имплементируют класс
getName Получение полного имени класса
getSimpleName Получение названия класса
getModifiers()

Доступ к модификаторам можно получить с помощью Class объекта.

Модификаторы представляют собой ключевые слова public , static , interface и т. д. Получаем модификаторы с помощью метода getModifiers() :

 Class personClass = Person.class; int classModifiers = personClass.getModifiers(); 

Результат выполнения находится в переменной int , где каждый модификатор — это битовый флаг, который может быть установлен или сброшен. Мы можем проверить модификаторы, используя методы в классе java.lang.reflect.Modifier :

 import com.company.Person; import java.lang.reflect.Modifier; public class TestReflection < public static void main(String[] args) < ClasspersonClass = Person.class; int classModifiers = personClass.getModifiers(); boolean isPublic = Modifier.isPublic(classModifiers); boolean isStatic = Modifier.isStatic(classModifiers); boolean isFinal = Modifier.isFinal(classModifiers); boolean isAbstract = Modifier.isAbstract(classModifiers); boolean isInterface = Modifier.isInterface(classModifiers); System.out.printf("Class modifiers: %d%n", classModifiers); System.out.printf("Is public: %b%n", isPublic); System.out.printf("Is static: %b%n", isStatic); System.out.printf("Is final: %b%n", isFinal); System.out.printf("Is abstract: %b%n", isAbstract); System.out.printf("Is interface: %b%n", isInterface); > > 

Вспомни, как выглядит объявление нашего класса Person :

 public class Person

В результате получаем вот такой вывод на экран:

Class modifiers: 1
Is public: true
Is static: false
Is final: false
Is abstract: false
Is interface: false

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

 public abstract class Person

Class modifiers: 1025
Is public: true
Is static: false
Is final: false
Is abstract: true
Is interface: false

У нас изменился модификатор доступа, а значит и данные, которые будут возвращаться через наши статические методы класса Modifier .

getPackage()

Зная только класс, мы можем получить информацию о пакете:

 Class personClass = Person.class; final Package aPackage = personClass.getPackage(); System.out.println(aPackage.getName()); 
getSuperclass()

Имея доступ к объекту класса, можем получить доступ к его суперклассу:

 public static void main(String[] args) < ClasspersonClass = Person.class; final Class superclass = personClass.getSuperclass(); System.out.println(superclass); > 

В результате получим всем известный класс Object :

 class java.lang.Object 

А вот если у нашего класса будет класс-родитель, то мы увидим именно его:

 package com.company; class Human < // some info >public class Person extends Human < private int age; private String name; // some info >

И тогда в результате мы получим уже наш суперкласс:

 class com.company.Human 
getInterfaces()

Список интерфейсов, реализуемых данным классом, можно получить так:

 public static void main(String[] args) < ClasspersonClass = Person.class; final Class[] interfaces = personClass.getInterfaces(); System.out.println(Arrays.toString(interfaces)); > 

И не забудем модифицировать наш класс Person :

 public class Person implements Serializable

[interface java.io.Serializable]

Класс может реализовать много интерфейсов. Поэтому возвращается массив объектов Class . В Java Reflection API интерфейсы также представлены объектами типа Class .

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

getName() & getSimpleName() & getCanonicalName()

Напишем пример для примитива, вложенного класса, аннонимного класса и класса String :

 public class TestReflection < public static void main(String[] args) < printNamesForClass(int.class, "int class (primitive)"); printNamesForClass(String.class, "String.class (ordinary class)"); printNamesForClass(java.util.HashMap.SimpleEntry.class, "java.util.HashMap.SimpleEntry.class (nested class)"); printNamesForClass(new java.io.Serializable() < >.getClass(), "new java.io.Serializable()<>.getClass() (anonymous inner class)"); > private static void printNamesForClass(final Class clazz, final String label) < System.out.printf("%s:%n", label); System.out.printf("\tgetName()):\t%s%n", clazz.getName()); System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName()); System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName()); System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName()); >> 

Результат нашей программы:

int class (primitive):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (ordinary class):
getName()): java.lang.String
getCanonicalName()): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (nested class):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName()): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable()<>.getClass() (anonymous inner class):
getName()): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1

Теперь давай разберем результаты нашей программы:

  • getName() возвращает имя сущности.
  • getCanonicalName() возвращает каноническое имя базового класса, как определено спецификацией языка Java. Возвращает null, если базовый класс не имеет канонического имени (то есть если это локальный или анонимный класс или массив, тип компонента которого не имеет канонического имени).
  • getSimpleName() возвращает простое имя базового класса, как указано в исходном коде. Возвращает пустую строку, если базовый класс является анонимным.
  • getTypeName() возвращает информативную строку для имени этого типа.

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

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

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

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

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

Как мы видим, это самый обычный класс. Конструктор с параметрами закомментирован не просто так, мы к этому еще вернемся. Если вы внимательно просмотрели содержимое класса, то наверняка увидели отсутствие getter ’a для поля name . Само поле name помечено модификатором доступа private , обратиться к нему вне самого класса у нас не выйдет => мы не можем получить его значение. “Так в чем проблема? — скажете вы. — Допиши getter или измени модификатор доступа”. И вы будете правы, но, что если MyClass находится в скомпилированной aar библиотеке или в другом закрытом модуле без доступа к редактированию, а на практике такое случается крайне часто. И какой-то невнимательный программист просто забыл написать getter . Самое время вспомнить о рефлексии! Попробуем добраться до private поля name класса MyClass :

 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. Связь между Class и ClassLoader мы затрагивать не будем, т.к. это не есть тема статьи. Далее, чтобы получить поля этого класса нужно вызвать метод getFields() , этот метод вернет нам все доступные поля класса. Нам это не подходит, так как наше поле private , поэтому используем метод getDeclaredFields() , этот метод также возвращает массив полей класса, но теперь и private и protected . В нашей ситуации мы знаем имя поля, которое нас интересует, и можем использовать метод getDeclaredField(String) , где String — имя нужного поля. Примечание: getFields() и getDeclaredFields() не возвращают поля класса-родителя! Отлично, мы получили объект Field с ссылкой на наш name . Т.к. поле не было публичным (public) следует дать доступ для работы с ним. Метод setAccessible(true) разрешает нам дальнейшую работу. Теперь поле name полностью под нашим контролем! Получить его значение можно вызовом get(Object) у объекта Field , где Object — экземпляр нашего класса MyClass . Приводим к типу String и присваиваем нашей переменной name . На тот случай если у нас вдруг не оказалось setter ’a, для установки нового значения полю name можно использовать метод set :

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

Поздравляю! Вы только что овладели базовым механизмом рефлексии и смогли получить доступ к private полю! Обратите внимание на блок try/catch и типы обрабатываемых исключений. IDE сама укажет на их обязательное присутствие, но по их названию итак ясно зачем они здесь. Идем дальше! Как вы могли заметить, наш MyClass уже имеет метод для вывода информации о данных класса:

 private void printData()

Но этот программист и тут наследил. Метод находится под модификатором доступа 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(); >> 

Здесь примерно такая же процедура как и с получением поля — получаем нужный метод по имени и даем доступ к нему. И для вызова объекта Method используем invoke(Оbject, Args) , где Оbject — все также экземпляр класса MyClass . Args — аргументы метода — наш таковых не имеет. Теперь для вывода информации мы используем функцию printData :

 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 (во время выполнения программы)! Мы можем создать объект класса по полному имени этого класса. Полное имя класса — это имя класса, учитывая путь к нему в package .

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

В моей иерархии package полным именем MyClass будет “ reflection.MyClass ”. Также узнать имя класса можно простым способом (вернет имя класса в виде строки):

 MyClass.class.getName() 

Создадим экземпляр класса с помощью рефлексии:

 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 = ; 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(); > 

Таким образом получаем все конструкторы и все параметры к ним. В моем примере идет обращение к конкретному конструктору с конкретными уже известными параметрами. И для вызова этого конструктора используем метод newInstance , в котором указываем значения этим параметрам. Точно так же будет и с invoke для вызова методов. Возникает вопрос: где может пригодится рефлексивный вызов конструкторов? Современные технологии java, как уже говорилось в начале, не обходятся без Reflection API. Например, DI (Dependency Injection), где аннотации в сочетании с рефлексией методов и конструкторов образуют популярную в Android разработке библиотеку Dagger. После прочтения этой статьи вы с уверенностью можете считать себя просвещенным в механизмы Reflection API. Темной стороной java рефлексия называется не зря. Она напрочь ломает парадигму ООП. В java инкапсуляция служит для сокрытия и ограничения доступа одних компонентов программы к другим. Используя модификатор private мы подразумеваем, что доступ к этому полю будет только в пределах класса, где это поле существует, основываясь на этом мы строим дальнейшую архитектуру программы. В этой статье мы увидели, как с помощью рефлексии можно пробираться куда угодно. Хорошим примером в виде архитектурного решения является порождающий шаблон проектирования — Singleton . Основная его идея в том, чтобы на протяжении всей работы программы класс, реализующий этот шаблон был только в одном экземпляре. Осуществляется это при помощи установки конструктору по умолчанию private модификатор доступа. И будет очень нехорошо, если какой-то программист со своей рефлексией будет плодить такие классы. Кстати, есть очень интересный вопрос, который я недавно услышал от своего сотрудника: может ли быть у класса, реализующий шаблон Singleton , наследники? Неужели в этом случае бессильна даже рефлексия? Пишите ваши feedback’и по статье и ответ в коментарии, а также задавайте свои вопросы! Истинная Сила Reflection API раскрывается в комбинации c Runtime Annotations, о чем мы, возможно, поговорим в следующей статье про темную сторону Java. Спасибо за внимание!

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

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