提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
一、反射概述
1、什么是反射?
Java反射机制是在运行状态中,对于任何一个类,都可以知道它所有的成员变量和方法,对于任何一个对象,都可以调用它的任意的属性和方法。这种动态获取信息和动态调用对象的方法的功能称为反射机制。
2、为什么要使用反射?
- 反射机制是Java框架的灵魂,它可以通过外部文件配置,在不修改源码的情况下能够控制程序,符合设计模式中的开闭原则。
二、深入理解反射机制
1、反射机制原理图
- Java程序在计算机中有三个阶段:源码阶段 / 编译阶段、类加载阶段、运行阶段
- 在编译阶段时,会将源代码经过javac编译成.class字节码文件
- 类加载阶段时,将.class字节码文件通过类加载器 创建一个类对象,并把它放在堆中。此时类中的成员变量和方法都会被当作对象来存储。
- 类加载完之后,就会生成一个对象,(在堆中),该对象知道它是属于哪个类的,此时我们就得到了这个对象。就能够操作它的属性和方法。
例如:在运行阶段时,new 了一个Cat对象,在new对象的时候会有一个类加载阶段,这个时候会通过类加载器把字节码文件加载到内存中的堆里去,同时在方法区会生成该对象的字节码的二进制数据。会生成一个对应的Class类对象(有成员信息),此时底层会将成员变量、构造器、方法等都看做成对象来对待。加载完成后就会生成一个Cat对象,该对象知道它是属于Cat类的。
2、反射机制能做什么?
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时得到任意一个对象的方法和属性。
- 生成动态代理
3、反射相关的主要类
- java.lang.Class : 代表一个类,Class类表示在某个类加载后在堆中生成的对象。
- java.lang.reflect.Method : 代表类的方法,Method对象表示某个类的方法。
- java.lang.reflect.Field : 代表类的成员变量,Field对象表示某个类的字段。
- java.lang.reflect.Constructor : 代表类的构造器,Constructor对象表示某个类的构造器。
注意:发生java.lang.InstantiationException的异常时,检查下列几点:
- 看要实例化的对象是否是一个接口或者抽象类等不可被实例化的类。通常将构造方法改为public就好。
- 持久化引起的异常,看要实例化的对象的类中是否含有一个无参构造器,如果没有,则添加一个无参构造器。(在Hibernate中就有明确的要求:每一个持久化类都必须带一个不带参数的构造方法。)
使用代码:
1、先创建一个Cat类
public class Cat {
public String name;
public int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public Cat() {
}
public void hi() {
System.out.println("喵喵叫...");
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2、创建Test类测试
获取Cat类对应的Class对象,并得到如下信息
Class cls = Class.forName("practice.reflection_.Cat");
//2、输出该对象属于哪个类practice.reflection_.practice.reflection_.Cat
System.out.println(cls);
//3、输出的类型是它的运行类型java.lang.Class
System.out.println(cls.getClass());
//4、得到包名practice.reflection_
System.out.println(cls.getPackage().getName());
//5、得到全类名practice.reflection_.practice.reflection_.Cat
System.out.println(cls.getName());
通过cls创建对象实例,可以通过API得到对应的字段、构造器、成员方法
/**
* 6、通过cls创建对象实例
* 在newInstance的时候,一定要注意该Cat的构造方法是否含有一个无参构造器
* 若没有无参构造器则会报错。
*/
Object cat = cls.newInstance();
//7.1、得到public类型的字段并输出
Field name = cls.getField("name");//这里name属性是public的,所以没报错
System.out.println(name.get(cat));
//7.2、给属性赋值
name.set(cat,"小花");
System.out.println(name.get(cat));
//7.3得到所有的字段属性(public类型)
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
//8、得到想要的方法并执行。
Method hi = cls.getMethod("hi");
hi.invoke(cat);
//9、得到所需的构造器
Constructor constructor1 = cls.getConstructor();//()可以传入指定构造参数类型
System.out.println(constructor1);
System.out.println(cls.getConstructor(String.class));//得到构造器为一个String类型的构造器对象并输出
Constructor[] constructors = cls.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName());
}
4、Class类分析
Ⅰ.基本原理
注意事项:
- Class类也是类,因此也继承Object类。
- Class不是new 出来的,而是系统创建的。
- 对于某个类的Class类对象,内存中只有一份,因此类只加一次。
- 每个类的对象都记得它是由哪个Class实例来生成。
- 通过Class类对象可以完整的知道一个类的完整结构,通过一系列的API。
- 类对象是存储在堆中的。
- 类的字节码二进制数据,是存放在方法区的。(也称为元数据)
- 在new 一个对象的时候,会进入到类加载器中(ClassLoader类),然后调用loadClass方法。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
- 利用反射来创建一个对象的时候,最终还是会进入到loadClass方法。
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
Ⅱ.获取Class类的几种方式
- 在编译阶段获取:Class.forName
- 在类加载阶段获取:类.Class
- 在运行阶段获取:对象.getClass()
- 通过类加载器获取类对象
代码演示:
- 方式一:已知一个类的类名,并且该类在类路径下,可通过Class类的静态方法forName()获取。
应用场景:多用于配置文件,读取类的全路径,加载类
public static void main(String[] args) throws ClassNotFoundException {
/**
* 方式一:已知一个类的类名,并且该类在类路径下,可通过Class类的静态方法forName()获取。
* 应用场景:多用于配置文件,读取类的全路径,加载类
*/
String classAllPath = "practice.reflection_.Cat";//通过读取配置文件获得
Class cls1 = Class.forName(classAllPath);
System.out.println(cls1);
- 方式二:若已知具体的类,可通过类.class来得到,此方法最为安全可靠
* 应用场景:多用于参数传递;
/**
* 方式二:若已知具体的类,可通过类.class来得到,此方法最为安全可靠
* 应用场景:多用于参数传递;
*/
Class cls2 = Cat.class;
System.out.println(cls2);
- 方式三:若已知对象实例,可通过对象实例.getClass()得到
* 应用场景:有对象实例,可以得到它的运行类型。
/**
* 方式三:若已知对象实例,可通过对象实例.getClass()得到
* 应用场景:有对象实例,可以得到它的运行类型。
*/
Cat cat = new Cat();
Class cls3 = cat.getClass();
System.out.println(cls3);
- 方式四:通过类加载器获取
* 应用场景:在加载阶段
/**
* 方式四:通过类加载器获取
* 应用场景:在加载阶段
*/
//先得到类加载器classLoader
ClassLoader classLoader = cat.getClass().getClassLoader();
Class cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);
- 其它获取方式
/**
* 其它方式:
* 基本数据类型int.class
* 包装类Integer.TYPE
* 都可以得到对应的Class
*/
Class<Integer> integerClass = int.class;
System.out.println(integerClass);
Class<Integer> type = Integer.TYPE;
System.out.println(type);
//int 和Interger在底层是同一个对象。
System.out.println(type.hashCode()==integerClass.hashCode());
}
Ⅲ.哪些类有Class对象
- 外部类、四大内部类、接口
- 枚举、注解
- 数组、基本数据类型、void
5、类加载
Ⅰ.基本说明
- 静态加载:编译时就加载相关的类,如果没有则会报错。
- 动态加载:只有执行到该代码时才会加载相关的类,如果没有才报错;运行时没有执行到该代码即使不存在相关的类也不会报错。
- 类加载时机
- 当创建对象时new 一个对象(静态加载)
- 当子类被加载时,父类也被加载(静态加载)
- 调用类中的静态成员时(静态加载)
- 通过反射(动态加载)
Ⅱ.类加载的三个阶段
1、加载 ——>2、连接(验证—>准备—>解析)——>3、初始化
1、加载
- 加载阶段的目的主要是从不同的数据源(可能是.class文件、jar包)将字节码转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象。
2.1连接阶段——验证
- 目的是确保当前Class文件的字节流中的信息符合JVM的要求,并且不会危害到JVM自身的安全
- 包括文件格式验证(是否以魔数Ox cafe babe,)、字节码验证、元数据验证和符号引用验证。
- 可以使用 -Xverify : none 参数来关闭大部分的类验证措施,可以缩短虚拟机类加载的时间。
2.2连接阶段——准备
- 该阶段JVM对静态变量分配内存并进行默认初始化(根据数据类型来确定默认初始值);这些变量所使用的内存都将在方法区中分配。(不是静态变量不会分配内存)
分配方法如下:
public class A {
//n1不是静态变量,不会分配内存
public int n1 = 20;
//n2是静态变量,会分配内存,初始值为0
public static int n2 = 18;
//static final n3为常量,一旦赋值便不可改变,直接分配n3 = 30
public static final int n3 = 30;
}
2.3连接阶段——解析
- 虚拟机将常量池内的符号引用替换为直接引用的过程。
- 符号引用:
3、初始化阶段
-
到初始化阶段,才会真正的执行类中定义的Java代码,此阶段是执行<clinit()>方法的过程。
-
clinit() 方法是编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作 和静态代码块中的语句。
-
虚拟机会保证一个类的clinit()方法在多线程环境中被正确的加锁、同步,如果多个线程去同时去初始化一个类,那么只会有一个线程去执行这个类的clinit()方法,其它线程都要活动阻塞,直到这个线程执行clinit()方法完毕。
三、通过反射的常用操作
1、获取类的结构信息
第一组:java.lang.Class类
Car类:
@Deprecated//注解
class Car implements Usb{
private int price;
public String brand;
public Car(int price, String brand) {//有参(2个)构造器
this.price = price;
this.brand = brand;
}
public Car(int price) {//一个有参构造器
this.price = price;
}
public Car() {//无参构造
}
public void doWork(){
System.out.println("执行car的公共doWork方法");
}
private void stopWork(){
System.out.println("执行car的私有stopWork方法");
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"price=" + price +
", brand='" + brand + '\'' +
'}';
}
}
interface Usb{
}
测试java.lang.Class类的常用API
public class RelectUtils{
public static void main(String[] args) throws ClassNotFoundException {
String fileAllPath = "practice.reflec_01.Car";
Class cls = Class.forName(fileAllPath);//得到Class类对象
//1、getName:获取全类名
System.out.println("1==============");
System.out.println(cls.getName());
System.out.println("2--------------");
//2、getSimpleName:获取简单类名
System.out.println(cls.getSimpleName());
System.out.println("3--------------");
//3、getFiles:获取所有public修饰的属性,包含本类以及父类的
Field[] fields = cls.getFields();
for (Field field : fields) {//应该获取brand属性,因为它是public的
System.out.println(field.getName());
}
System.out.println("4--------------");
//4、getDeclaredFields:获取本类中所有属性
Field[] declaredFields = cls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName());
}
System.out.println("5--------------");
// 5、getMethods:获取所有public方法,包含本类及父类的
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
System.out.println("6------------------");
// 6、getDeclaredMethods:获取所有方法(不包含父类)
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName());
}
System.out.println("7-------------------");
//7、getConstrctors:获取本类所有Public修饰的构造器
Constructor[] constructors = cls.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName());
}
System.out.println("8---------------------");
//8、getDeclaredConstrctors:获取本类所有的构造器
Constructor[] declaredConstructors = cls.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor.getName());
}
System.out.println("9---------------------");
//9、getPackage:以Package形式返回包信息
Package aPackage = cls.getPackage();
System.out.println(aPackage.getName());
System.out.println("10---------------------");
//10、getSuperclass:以class形式返回父类信息
Class superclass = cls.getSuperclass();
System.out.println(superclass.getName());
System.out.println("11-------------------");
//11、以Class[]形式返回接口信息。
Class[] interfaces = cls.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface.getName());
}
System.out.println("12----------------");
Annotation[] annotations = cls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解"+annotation);
}
}
}
第二组:java.lang.reflect.Field类
演示代码:
System.out.println("=========第二组==========");
Field[] declaredFields1 = cls.getDeclaredFields();
System.out.println("1、--------------");
//1、getModifiers以int形式返回修饰符
//默认修饰符为0,public是1,private是2,protected是4
//static是8,final是16,public static = 1 + 8 = 9
for (Field field : declaredFields1) {
System.out.println(field.getModifiers());
}
System.out.println("2、--------------------");
//2、getType:以Class形式返回类型
for (Field field : declaredFields1) {
System.out.println(field.getType());
}
System.out.println("3、-------------------");
//3、getName:返回属性名
for (Field field : declaredFields1) {
System.out.println(field.getName());
}
第三组:java.lang.reflect.Method类
代码演示:
System.out.println("=========第三组==========");
System.out.println("1、--------------");
/**
* 1、getModifiers:以int形式返回修饰符
* 2、getName:返回方法名
* 3、getReturnType:以class形式获取返回类型
* 4、getParameterTypes:以class[]返回形式参数数组
*/
Method[] methods1 = cls.getMethods();
for (Method method : methods1) {
System.out.println("返回修饰符类型(int)"+method.getModifiers()
+"\t方法名:"+method.getName()+"\t以Class形式获取返回类型"+method.getReturnType());
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该方法的形参类型"+parameterType);
}
}
第四组:java.lang.reflect.Constructor类
System.out.println("=========第四组==========");
Constructor[] constructors1 = cls.getConstructors();
for (Constructor constructor : constructors1) {
System.out.println("返回构造器名:(全类名)"+constructor.getName()+
"\t以int类型返回修饰符:"+ constructor.getModifiers());
Class[] parameterTypes = constructor.getParameterTypes();//以class类型返回形式参数数组
for (Class parameterType : parameterTypes) {
System.out.println("返回参数类型:"+parameterType);
}
}
2、通过反射创建对象
- 两种方式
- 方式一:调用类中的public修饰的无参构造器
- 方式二:调用类中指定的构造器
- class类相关方法
- newInstance : 调用类中的无参构造器,获取对应类的对象。
- getConstructor : (Class …clazz ),根据参数列表,获取对应的public构造器对象。
- getDecalaredConstructor : (Class …clazz),获取所有对应的构造器对象。
- constructor相关方法
- setAccessible : 暴破
- newInstance (Object …object ):调用构造器
代码演示:
测试
1、通过public无参构造来创建对象实例
String fileAllPath = "practice.reflec_01.Car";
Class cls = Class.forName(fileAllPath);//得到Class类对象
//1、通过public无参构造来创建对象实例
Object car1 = cls.newInstance();//1.1直接调用Instance方法得到一个对象
System.out.println(car1);
2、通过Public的有参构造器创建对象实例
//2、通过Public的有参构造器创建对象实例
//2.1得到class对象的构造器,并传入构造器参数对应的class类型
Constructor constructor = cls.getConstructor(int.class);
//2.2通过newInstance创建该构造器对应的对象,并传入自己指定的值
Object car2 = constructor.newInstance(2880000);
System.out.println(car2);
3、通过非public的有参构造器来创建对象实例
//3、通过非public的有参构造器来创建对象实例
//3.1创建一个可以得到全部修饰类型的构造器,并指定构造器的参数对应的Class类型
Constructor declaredConstructor = cls.getDeclaredConstructor(String.class);
//3.2暴力破解的方式得到非Public类型的构造器对象。使用反射可以得到私有属性
declaredConstructor.setAccessible(true);
//3.3创建需要的对象,并传入需要设置或修改的值。
Object car3 = declaredConstructor.newInstance("保时捷911");
System.out.println(car3);
}
演示类:Car的基本信息设置
class Car implements Usb {
private int price = 20;
public String brand = "宝马7";
public Car(int price, String brand) {//有参(2个)构造器
this.price = price;
this.brand = brand;
}
public Car(int price) {//一个有参构造器
this.price = price;
}
public Car() {//无参构造
}
private Car(String brand){
this.brand = brand;
}
public void doWork() {
System.out.println("执行car的公共doWork方法");
}
private void stopWork() {
System.out.println("执行car的私有stopWork方法");
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"price=" + price +
", brand='" + brand + '\'' +
'}';
}
}
interface Usb {
}
3、通过反射访问类中成员
访问属性
代码演示:
//通过反射来操作对象的成员变量
Object car = cls.newInstance();//得到一个对象
//访问私有属性需要暴破并且getDeclaredField 而不是getField
Field price = cls.getDeclaredField("price");//设置想要的设置属性,传入属性名称
price.setAccessible(true);//通过反射的暴力破解(取消安全检查),允许访问私有属性
price.set(car,9900000);//设置属性值
price.set(null,80000000);//Object参数也可以是null,因为它是static修饰的,static属于所有类。
//不是静态的不能置空!!!!
System.out.println(car);
操作方法
代码演示:
//通过反射来操作对象的成员方法
Object car5 = cls.newInstance();//1、先得到对象实例
//2、通过对象实例获取所有可操作的方法,并传入要获取的方法名,以及形参列表的Class类型
Method doWork = cls.getDeclaredMethod("doWork", int.class);
doWork.setAccessible(true);//想要通过反射访问私有的方法需要暴破
doWork.invoke(car5,0);//4、调用invoke方法,传入实例对象,以及对应的实参。
//如果是静态方法,car5可以替换为Null,否则必须写对应的对象实例
总结
- 反射的重点在于掌握类加载的过程(三个阶段),分别是加载、连接(验证、准备、解析)、初始化。然后就是创建对象实例的三种方式(无参构造newInstance,public有参构造,非public有参构造),以及常用类的方法的使用。