«Отражение» относится к операции, в которой программа может динамически получать информацию о типе класса во время выполнения. Это душа современных фреймворков.Некоторые механизмы автоматизации, которые могут предоставить почти все фреймворки, реализуются путем отражения, поэтому различные фреймворки не позволяют вам переопределить конструктор без аргументов по умолчанию, потому что фреймворк должен начинаться с механизма отражения. использует конструктор без аргументов для создания экземпляров.
В общем, «размышление» стоит вашего времени на изучение.Хотя у большинства людей редко есть возможность писать фреймворки, это поможет вам понять различные фреймворки. Я не ожидаю, что вы глубоко поймете «отражение» благодаря изучению этой статьи, но, по крайней мере, убедитесь, что вы понимаете основные принципы и использование «отражения».
Информация о типе класса
Был введен механизм загрузки класса виртуальной машины.Мы упомянули, что каждый тип будет загружен в «область методов» памяти виртуальной машины при первом использовании, включая поля атрибутов, определенные в классе, код байтов метода , и т.д.
В Java класс java.lang.Class используется для указания на информацию о типе.Через этот объект класса мы можем получить всю внутреннюю информацию о классе. Есть три основных способа получить объект Class.
имя класса.класс
Этот метод относительно прост, если вы используете имя класса, чтобы щелкнуть класс, вы можете получить информацию о типе типа в области метода. Например:
Object.class;
Integer.class;
int.class;
String.class;
//等等
метод getClass
В классе Object есть такой метод:
public final native Class<?> getClass();
Это собственный метод, и подклассы не могут его переопределять, поэтому теоретически все типы экземпляров имеют один и тот же метод getClass. Он также очень прост в использовании:
Integer integer = new Integer(12);
integer.getClass();
метод forName
forName является одним из наиболее часто используемых методов для получения типа класса. Он позволяет передать полное имя класса, и метод вернет объект класса, представляющий этот тип в области методов. Если класс не был загружен в область метода, forName будет выполняться в первую очередь загрузка класса.
public static Class<?> forName(String className) {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
Поскольку информация о типе класса в области метода однозначно определяется загрузчиком класса и полным именем класса, если вы хотите найти такой класс, вы должны предоставить загрузчик класса и полное имя класса.Метод forName использует загрузчик классов вызывающего абонента по умолчанию.
Конечно, класс Class также имеет перегрузку forName, которая позволяет вам передать загрузчик класса и полное имя класса, чтобы оно соответствовало информации о типе области метода.
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader){
//.....
}
До сих пор вы можете получить информацию о типе любого класса с помощью этих методов, и все атрибуты поля, таблицу методов и другую информацию о классе можно получить с помощью этого объекта класса.
Свойства отраженного поля
Методы получения свойств полей в Class в основном следующие:
- public Field[] getFields(): возвращает все общедоступные свойства типа, включая свойства родительского класса.
- public Field getField (имя строки): возвращает соответствующее поле в соответствии с именем поля.
- public Field[] getDeclaredFields(): возвращает все поля, объявленные в этом типе, включая непубличные украшения, но исключая поля в родительском классе.
- публичное поле getDeclaredField (имя строки): то же самое
Конечно, экземпляр Field содержит всю информацию о свойстве класса, включая имя поля, модификаторы доступа и тип поля. Кроме того, Field также предоставляет большое количество методов для управления значением этого атрибута.Передавая экземпляр класса, вы можете напрямую использовать экземпляр Field для управления значением текущего атрибута поля экземпляра.
Например:
//定义一个待反射类
public class People {
public String name;
}
Class<People> cls = People.class;
Field name = cls.getField("name");
People people = new People();
name.set(people,"hello");
System.out.println(people.name);
Программа выведет:
hello
На самом деле это тоже очень просто: метод set будет получать, есть ли в объекте People поле, представленное именем, и если да, то присваивать этому полю строку hello.
Весь класс Field в основном состоит из двух частей: первая часть представляет собой описание информации о свойствах поля, таких как имя, тип, периферийный класс, объект класса и т. д. Вторая часть представляет собой большое количество методов получения и установки. для косвенной работы произвольных периферийных классов Текущее значение свойства экземпляра.
метод отражения
Точно так же класс Class также предоставляет четыре метода для получения свойств метода:
- public Method[] getMethods(): возвращает все общедоступные методы, в том числе в родительском классе.
- общедоступный метод getMethod (имя строки, Class>... parameterTypes): возвращает указанный метод
- public Method[] getDeclaredMethods(): возвращает все методы, объявленные в этом классе, включая непубличные измененные, но исключая методы в родительском классе.
- общедоступный метод getDeclaredMethod (имя строки, класс >... параметры): то же самое
Метод абстрактно представляет метод, а также имеет поля и методы, которые описывают основную информацию о методе, такую как имя метода, набор параметров метода, тип возвращаемого значения метода, набор типов исключений, аннотацию метода и т. д.
В дополнение к этому есть метод invoke для косвенного вызова этого метода других экземпляров, например:
public class People {
public void sayHello(){
System.out.println("hello wrold ");
}
}
Class<People> cls = People.class;
Method sayHello = cls.getMethod("sayHello");
People people = new People();
sayHello.invoke(people);
Вывод программы:
hello wrold
конструктор отражения
Для Constructor класс Class по-прежнему предоставляет четыре метода для получения экземпляров:
- public Constructor>[] getConstructors(): возвращает все общедоступные оформленные конструкторы.
- public Constructor>[] getDeclaredConstructors(): возвращает все конструкторы, игнорируя модификаторы доступа
- открытый конструктор getConstructor(Class>... parameterTypes): с указанными параметрами
- открытый конструктор getDeclaredConstructor(Class>... parameterTypes): то же самое
Constructor по сути тоже является методом, но он не имеет возвращаемого значения, поэтому базовое внутреннее содержимое похоже на Method, за исключением того, что в классе Constructor есть метод newInstance для создания экземпляра объекта типа Class.
//最简单的一个反射创建实例的过程
Class<People> cls = People.class;
Constructor c = cls.getConstructor();
People p = (People) c.newInstance();
Выше мы кратко представили основные способы использования отражения, но они очень простые.Давайте рассмотрим использование отражения в сочетании с некоторыми более сложными типами, такими как массивы, дженерики, аннотации и т. д.
Другие детали отражения
отражение и массивы
Как мы все знаем, массив — это особый тип, который, по сути, динамически генерируется виртуальной машиной во время выполнения, поэтому при рассмотрении этого типа он немного отличается.
public native Class<?> getComponentType();
В классе есть метод, который возвращает базовый тип элементов экземпляра класса массива. Только когда текущий объект Class представляет тип массива, этот метод вернет фактический тип элементов массива и вернет null во всех остальных случаях.
Конечно, следует отметить, что тип, представляющий массив, динамически создается виртуальной машиной, он напрямую наследует класс Object и все операции, связанные с классом массива, такие как присвоение значения элементу или получение значения. длина массива, прямо соответствует одной инструкции по работе с массивом виртуальной машины.
Кроме того, поскольку класс массива динамически создается непосредственно средой выполнения виртуальной машины, вы не можете получить конструктор из экземпляра типа массива класса, а компилятор вообще не имеет возможности создать конструктор по умолчанию для класса. Таким образом, вы не можете использовать Constructor для создания экземпляра объекта этого класса обычным способом.
Если вы настаиваете на попытке использовать конструктор для создания нового экземпляра, среда выполнения сообщит вам, что конструктор не может быть сопоставлен. нравится:
Class<String[]> cls = String[].class;
Constructor constructor = cls.getConstructor();
String[] strs = (String[]) constructor.newInstance();
Вывод консоли:
Он говорит вам, что в экземпляре класса вообще нет конструктора без аргументов. Значит, у нас нет способа динамически создать массив?
Конечно, нет, в Java есть класс java.lang.reflect.Array, который предоставляет некоторые статические методы для динамического создания и получения типа массива.
//创建一个一维数组,componentType 为数组元素类型,length 数组长度
public static Object newInstance(Class<?> componentType, int length)
//可变参数 dimensions,指定多个维度的单维度长度
public static Object newInstance(Class<?> componentType, int... dimensions)
На мой взгляд, это два самых важных метода в классе Array.Конечно, в классе Array есть и некоторые другие методы, которые используются для получения заданного элемента позиции заданного массива, которые здесь повторяться не будут.
Это полностью связано с тем, что тип массива не компилируется и не генерируется обычным компилятором, а динамически создается виртуальной машиной, поэтому, если вы хотите создать экземпляр типа массива путем отражения, вы должны полагаться на связанный метод newInstance класса Тип массива.
Отражение и дженерики
Обобщения — это концепция в рамках компилятора Java, она может обеспечивать определенные проверки безопасности перед запуском программы, а отражение происходит во время выполнения, что означает, что если вы вызываете универсальный метод с помощью отражения, вы фактически обходите компиляцию. устройство проверено. Давайте посмотрим на кусок кода:
ArrayList<Integer> list = new ArrayList<>();
list.add(23);
//list.add("fads");编译不通过
Class<?> cls = list.getClass();
Method add = cls.getMethod("add",Object.class);
add.invoke(list,"hello");
System.out.println(list.get(1));
В конце концов вы обнаружите, что мы берем строку из целочисленного контейнера, потому что виртуальная машина просто находит информацию о типе класса ArrayList из области методов во время выполнения и анализирует свой метод добавления, а затем выполняет этот метод.
Это не похоже на общий вызов метода.Перед вызовом компилятор проверит, существует ли метод или нет, совпадают ли типы параметров и т. д., поэтому без этого уровня проверки безопасности компилятором более вероятно столкнуться с проблемы при вызове метода рефлексивно.
Кроме того, универсальный тип, о котором мы упоминали ранее, будет стерт после периода компиляции, но на самом деле информация о типе класса, представляющая тип, содержит некоторую базовую общую информацию, которую мы можем отразить.
Я не хочу, чтобы все видели это здесь.В классах, полях и методах есть связанные методы для получения универсального имени класса, используемого при определении класса или метода. Обратите внимание, что здесь сказано только имя, что-то вроде E и V.
Весь код, изображения, файлы в статье хранятся в облаке на моем GitHub:
(https://github.com/SingleYam/overview_java)
Добро пожаловать в официальную учетную запись WeChat: OneJavaCoder, все статьи будут синхронизированы в официальной учетной записи.