1 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Полное руководство по Java Reflection API. Рефлексия на примерах

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Читать еще:  Переносной жесткий диск не определяется что делать?

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

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

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

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

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

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

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

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

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

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

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

Из приведённого примера видно что поля 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 для печати их значений.

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

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

Читать еще:  Зал славы потребительской электроники: истории лучших гаджетов последних 50 лет, часть 1

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

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

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

Reflection API в Java

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

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

Пакет java.lang.reflect предоставляет много классов для реализации отражения. Java.Methods класса java.lang.Class используется для сбора полных метаданных определенного класса.

Класс в пакете java.lang.reflect

Ниже приведен список различных классов Java в java.lang.package для реализации

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

Методы, используемые в java.lang.Class

  • Public String getName(): возвращает имя класса.
  • public Class getSuperclass(): возвращает ссылку на супер класс
  • Открытый класс [] getInterfaces(): возвращает массив интерфейсов, реализованных указанным классом.

Как получить полную информацию о классе

Пример 1: Как получить метаданные класса

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

Мы получим метаданные следующего класса с именем Guru99Base.class:

Пример 2: Как получить метаданные переменной

Следующие примеры показывают, как получить метаданные переменной:

Здесь мы создаем класс с именем Guru99VariableMetaData .class с некоторыми переменными:

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

Метод getDeclaredFields() возвращает метаданные всех переменных только из указанного класса.

Reflection в Java — java.lang.reflect API туториал

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

Все классы для работы с reflection расположены в пакете java.lang.reflect. Это метод (Method), конструктор (Constructor), массив (Array), поле (Field) и многие другие.

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

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

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

Модификаторы в Java по спецификации представлены в виде целых чисел, например:

Для декодирования значений модификаторов используется класс Modifiers.

Заметьте, что в классе Class присутствуют пары методов, как например getFields и getDeclaredFields. Метод getFields возвращает только те поля, которые объявлены как public + public поля родительских классов, в то время как getDeclaredFields возвращает все поля текущего класса не зависимо от их видимости. Аналогично для getMethods и getConstructors.

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

Для получения аннотаций используется метод getAnnotations. Но метод вернет не сами классы аннотаций, а Proxy. Чтобы получить сам класс аннотации используйте метод annotationType().

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

1. С введением аннотаций в java 1.5 стало легко добавлять метаинформацию к классам, полям, методам и параметрам. По наличию аннотации (или ее отсутствию) можно во время выполнения программы принимать различного рода решения.

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

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

В частности, метод wrapperFor используется для определения класса-обертки для примитивных типов. Это удобно для последующего вызова метода valueOf. К сожалению, класс Character не содержит такого метода и его нужно обрабатывать особенным образом.

3. Продлжая тему о сервлетах. В веб-фреймворке, который писали мои друзья, используется следующий контракт: действие представляет собой вызов некоторого метода someMethod у некоторого класса SomeClass. При этом url имеет вид, например, somedomain.com/SomeAction.someMethod.

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

Для вызова, в метод invoke нужно передать инстанс, у которого и будет вызываться наш метод.

Обратите внимание, что InvocationTargetException оборачивает ошибку, которая может произойти в методе, вызываемом через reflection. Для получения самого исключения необходимо взять причину (cause) у инстанса InvocationTargetException.

4. Часто программистам приходится работать с чужими проприетарными библиотеками. Случается, что где-то в библиотеке значения по-умолчанию некоторых полей вас не устраивают, но изменить их через API нет возможности. Или некоторый метод закрыт модификатором доступа private.

Вместо декомпиляции-исправления-компиляции есть способ, который иногда выручает. Это установка флага acccessible. Давайте установим значение 10 для поля field класса Inaccessible:

Программа выведет сначала 0, а потом 10.

5. При написании своих контейнеров (на подобии IoC), вам может пригодиться способ для инстанциирования объектов с помощью default контруктров. Это можно сделать следующим образом:

Кстати, мой друг не так давно показал мне интересный способ инстанциирования объектов, который он обнаружил в sun’овских интерналсах. Нам даже посчастливилось воспользоваться данным способом на практике в виде такого вот метода:

А дело было вот в чем. Нам дали API бинарного протокола, который был написан мягко говоря плохо. Каждый transfer объект (а их около 40) содержал большое количество параметров (2-6), каждый из которых необходимо было проставлять через конструктор. Ни один из параметров на нашей стороне вообще не использовался — эти параметры заполнялись и использовались уже на стороне сервера.

Собственно метод instance очень упростил нам способ инстанциирования объектов.

Ссылка на основную публикацию
Статьи c упоминанием слов:
Adblock
detector