- Рефлексия кода, reflection
- Определение свойств класса
- Определение интерфейсов и конструкторов класса
- Определение полей класса
- Определение значений полей класса
- Определение методов класса
- Пример изменения значения закрытого поля класса
- Пример вызова метода, invoke
- Листинг класса Reflect
- Скачать пример
- Получение имени текущего выполняемого метода в Java
- Способ 1. Использование StackTrace
- Способ 2. Использование исключений
Рефлексия кода, reflection
Рефлексия (от reflexio — обращение назад) — это механизм исследования данных о программе во время её выполнения. Рефлексия в Java осуществляется с помощью Java Reflection API, состоящий из классов пакетов java.lang и java.lang.reflect. В информатике рефлексия означает процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения.
Java Reflection API позволяет получать информацию о конструкторах, методах и полях классов и выполнять следующие операции над полями и методами объекта/класса :
- определение класса объекта;
- получение информации о полях, методах, конструкторах и суперклассах;
- получение информации о модификаторах полей и методов;
- создание экземпляра класса, имя которого неизвестно до момента выполнения программы;
- определение и изменение значений свойств объекта/класса;
- вызов методов объекта/класса.
Примечание : в тексте используется объект/класс. При работе с объектом (реализацией класса) можно обращаться к полям и методам класса напрямую, если они доступны (не private). При работе с классом можно обращаться к методам класса с использованием Java Reflection API. Но класс необходимо получить из объекта.
Определение свойств класса
В работающем приложении для получения класса необходимо использовать метод forName (String className). Следующий код демонстрирует возможность создания класса без использования и с использованием Reflection :
// Без использования Reflection Foo foo = new Foo(); // С использованием Reflection Class foo = Class.forName("Foo"); // Загрузка JDBC-драйвера Class.forName("com.mysql.jdbc.Driver");
Метод класса forName(className) часто используется для загрузки JDBC-драйвера.
Методом getName() объекта Class можно получить наименование класса, включающего пакет (package) :
Class aclass = foo.getClass(); System.out.println (aclass.getName());
Для получения значения модификатора класса используется метод getModifiers(). Класс java.lang.reflect.Modifier содержит статические методы, возвращающие логическое значения проверки модификатора класса :
Class cls = foo.getClass(); int mods = cls.getModifiers(); if (Modifier.isPublic (mods)) < System.out.println("public"); >if (Modifier.isAbstract(mods)) < System.out.println("abstract");>if (Modifier.isFinal (mods))
Для получения суперкласса рефлексированного объекта (класса) необходимо использовать метод getSuperclass() :
Class cls = foo.getClass(); Class superCls = cls.getSuperClass();
Поскольку в Java отсутствует множественное наследование, то для получения всех предков следует рекурсивно вызвать метод getSuperclass() в цикле, пока не будет достигнут Object, являющийся родителем всех классов. Object не имеет родителей, поэтому вызов его метода getSuperclass() вернет null.
Определение интерфейсов и конструкторов класса
Для получения в режиме run-time списка реализующих классом интерфейсов, необходимо получить Class и использовать его метод getInterfaces(). В следующем примере извлекается список интерфейсов класса ArrayList :
Class cls = ArrayList.class; Class[] ifs = cls.getInterfaces(); System.out.println(«List of interfaces\n»); for(Class ifc : ifs)
Чтобы IDE (Eclipse) не предупреждала о необходимости определения типа класса
в коде были использованы generic’и. В консоль выводятся следующие интерфейсы, реализуемые классом ArrayList :
List of interfaces java.util.List java.util.RandomAccess java.lang.Cloneable java.io.Serializable
Метод класса getConstructors() позволяет получить массив открытых конструкторов типа java.lang.reflect.Constructor. После этого, можно извлекать информацию о типах параметров конструктора и генерируемых исключениях :
Class cls = obj.getClass(); Constructor[] constructors = cls.getConstructors(); for (Constructor constructor : constructors) < Class[] params = constructor.getParameterTypes(); for (Class param : params) < System.out.println(param.getName()); >>
Определение полей класса
Метод getFields() объекта Class возвращает массив открытых полей типа java.lang.reflect.Field, которые могут быть определены не только в данном классе, но также и в его родителях (суперклассе), либо интерфейсах, реализованных классом или его родителями. Класс Field позволяет получить имя поля, тип и модификаторы :
Class cls = obj.getClass(); Field[] fields = cls.getFields(); for (Field field : fields) < Classfld = field.getType(); System.out.println("Class name : " + field.getName()); System.out.println("Class type : " + fld.getName()); >
Если известно наименование поля, то можно получить о нем информацию с помощью метода getField() объекта Class.
Class cls = obj.getClass(); Field fld = cls.getField("fieldName");
Методы getField() и getFields() возвращают только открытые члены данных класса. Чтобы получить все поля класса, включая закрытые и защищенные, необходимо использовать методы getDeclaredField() и getDeclaredFields(). Данные методы работают точно так же, как и их аналоги getField() и getFields().
Определение значений полей класса
Класс Field содержит специализированные методы для получения значений примитивных типов: getInt(), getFloat(), getByte() и др. Для установки значения поля, используется метод set(). Для примитивных типов имеются методы setInt(), setFloat(), setByte() и др.
Class cls = obj.getClass(); Field field = cls.getField("fieldName"); String value = (String) field.get(obj); field.set(obj, "New value");
Ниже приведен пример изменения значения закрытого поля класса в режиме run-time.
Определение методов класса
Метод getMethods() объекта Class возвращает массив открытых методов типа java.lang.reflect.Method. Эти методы могут быть определены не только в классе, но также и в его родителях (суперклассе), либо интерфейсах, реализованных классом или его родителями. Класс Method позволяет получить имя метода, тип возвращаемого им значения, типы параметров метода, модификаторы и генерируемые исключения.
Class cls = obj.getClass(); Method[] methods = cls.getMethods(); for (Method method : methods) < System.out.println("Method name : " + method.getName()); System.out.println("Return type : " + method.getReturnType().getName()); Class[] params = method.getParameterTypes(); System.out.print("Parameters : "); for (Class paramType : params) < System.out.print(" " + paramType.getName()); >System.out.println(); >
Если известно имя метода и типы его параметров, то можно получить отдельный метод класса :
Class cls = obj.getClass(); Class[] params = new Class[] ; Method method = cls.getMethod("methodName", params);
Пример изменения значения закрытого поля класса
Чтобы изменить значение закрытого (private) поля класса необходимо получить это поле методом getDeclaredField () и вызвать метод setAccessible (true) объекта Field, чтобы открыть доступ к полю. После этого значение закрытого поля можно изменять, если оно не final. В следующем примере определен внутренний класс PrivateFinalFields с набором закрытых полей; одно из полей final. При создании объекта класса поля инициализируются. В методе main примера поочередно в закрытые поля вносятся изменения и свойства объекта выводятся в консоль.
import java.lang.reflect.Field; class PrivateFinalFields < private int i = 1; private final String s = "String S"; private String s2 = "String S2"; public String toString() < return "i = " + i + ", " + s + ", " + s2; >> public class ModifyngPrivateFields < public static void main(String[] args) throws Exception < PrivateFinalFields pf = new PrivateFinalFields(); Field f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); f.setInt(pf, 47); System.out.println("1. " + pf); f = pf.getClass().getDeclaredField("s"); f.setAccessible(true); f.set(pf, "MODIFY S"); System.out.println("2. " + pf); f = pf.getClass().getDeclaredField("s2"); f.setAccessible(true); f.set(pf, "MODIFY S2"); f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); f.setInt(pf, 35); System.out.println("3. " + pf); >>
В результате выполнения примера в консоль будут выведены следующие сообщения :
1. i = 47, String S, String S2 2. i = 47, String S, String S2 3. i = 35, String S, MODIFY S2
Из приведённого примера видно что поля 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 для печати их значений.
class Reflect < private String name; private int id; Reflect() < name = "Test"; >public int getId() < return id; >public void setId(int id) < this.id = id; >String getName() < return name; >public void setName(String name) < this.name = name; >public void setData(final int id, String name) < this.id = id; this.name = name; >@Override public String toString() < return "Reflect [ id : " + id + ", name : " + name + "]"; >>
Для тестирования объекта типа Reflect с помощью Java Reflection Api создадим класс ReflectionTest. В этот класс включим две процедуры getClassFields и getClassMethods, которые в режиме run-time распечатают всю информацию (описание полей и методов) о классе. Методы получают класс в качестве параметра. В процедурах сначала определяются массивы полей и методы; после этого их параметры распечатываются :
private void getClassFields(Class cls) < Field[] fields = cls.getDeclaredFields(); System.out.println("Class fields"); for (Field field : fields) < Classfld = field.getType(); System.out.println("Class name : " + field.getName()); System.out.println("Class type : " + fld.getName()); > > private void getClassMethods(Class cls) < Method[] methods = cls.getDeclaredMethods(); System.out.println("Class methods"); for (Method method : methods) < System.out.println("Method name : " + method.getName()); System.out.println("Return type : " + method.getReturnType().getName()); Class[] params = method.getParameterTypes(); System.out.print("Parameters : "); for (Class param : params) System.out.print(" " + param.getName()); System.out.println(); > >
В конструкторе класса ReflectionTest сначала вызываются процедуры определения полей и методов объекта/класса Reflect. После этого вызываются методы изменения значений и печати значений с использованием Reflection API. Для определения метода setData используется массив типов параметров. Вызов метода setData выполняется с передачей ему массива новых значений.
public class ReflectionTest < static Reflect reflect; public ReflectionTest() < getClassFields (reflect.getClass()); getClassMethods(reflect.getClass()); Classcls = reflect.getClass(); try < System.out.println("\n1. invoke method toString()\n"); Method method = cls.getMethod("toString"); System.out.println(method.invoke(reflect)); Class[] paramTypes; Object [] args; paramTypes = new Class[] ; method = cls.getMethod("setData", paramTypes); args = new Object[]; method.invoke(reflect, args); System.out.println("\n2. invoke method toString()\n"); method = cls.getMethod("toString"); System.out.println(method.invoke(reflect)); > catch (NoSuchMethodException e) < >catch (SecurityException e) < >catch (IllegalAccessException e) < >catch (IllegalArgumentException e) < >catch (InvocationTargetException e) < >> private void getClassFields(Class cls) < // код метода представлен выше >private void getClassMethods(Class cls) < // код метода представлен выше >public static void main(String[] args) < reflect = new Reflect(); new ReflectionTest(); System.exit(0); >>
В результате выполнения примера в консоль будут выведены представленные ниже сообщения. Методы setData и toString(), вызываемые с помощью Java Reflection API, вносят измнения в закрытые поля класса и распечатываются их значения.
Class fields Class name : name Class type : java.lang.String Class name : id Class type : int Class methods Method name : toString Return type : java.lang.String Parameters : Method name : getId Return type : int Parameters : Method name : setId Return type : void Parameters : int Method name : getName Return type : java.lang.String Parameters : Method name : setName Return type : void Parameters : java.lang.String Method name : setData Return type : void Parameters : int java.lang.String 1. invoke method toString() Reflect [ id : 999, name : Test] 2. invoke method toString() Reflect [ id : 123, name : New value]
Скачать пример
Исходный код рассмотренного примера вызова метода invoke с использованием Java Reflection API можно скачать здесь (989 байт).
Получение имени текущего выполняемого метода в Java
Ситуации, когда необходимо получить имя текущего выполняемого метода, могут встретиться довольно часто. Например, при отладке программы или при логировании.
Ситуации, когда необходимо получить имя текущего выполняемого метода, могут встретиться довольно часто. Например, при отладке программы или при логировании. Приведем пример: есть метод, в котором происходит некоторое действие, и результат этого действия записывается в лог. В логе необходимо указать, в каком именно методе было выполнено это действие.
В таком случае возникает вопрос: как получить имя текущего метода в Java? Для этого существует несколько способов.
Способ 1. Использование StackTrace
Java предоставляет возможность получить стек вызовов для текущего потока с помощью метода Thread.currentThread().getStackTrace() . Этот метод возвращает массив StackTraceElement , каждый из которых представляет собой элемент стека вызовов. Нулевой элемент массива ( [0] ) — это сам метод getStackTrace , первый элемент ( [1] ) — это метод, который вызвал getStackTrace , и так далее. Таким образом, чтобы получить имя текущего метода, нужно обратиться к второму элементу этого массива.
String methodName = Thread.currentThread().getStackTrace()[2].getMethodName();
Способ 2. Использование исключений
Другой способ получить стек вызовов — использовать исключения. Метод Throwable.getStackTrace() также возвращает массив StackTraceElement , аналогичный тому, который возвращает Thread.currentThread().getStackTrace() . Однако в этом случае нулевой элемент массива ( [0] ) — это метод, где было создано исключение.
String methodName = new Exception().getStackTrace()[0].getMethodName();
Оба этих способа позволяют получить имя текущего выполняемого метода. Однако стоит помнить, что оба способа имеют относительно высокую стоимость в плане производительности, поэтому их стоит использовать с умом и не злоупотреблять.