反射

一、反射机制

反射机制允许程序在执行期借助Reflection API取得任何类的内部信息(比如成员变量、构造器、成员方法等)并能操作对象的属性和方法,反射在设计模式和框架底层都会用到。

加载完类之后,在堆中就会产生一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。

三个阶段

1.1 Java反射机制可以完成

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时得到任意一个类所具有的成员变量和方法
  4. 在运行时调用任意一个对象的成员变量和方法
  5. 生成动态代理

1.2 反射主要的类

java.lang.Class 代表一个类,Class对象表示某个类加载后在堆中的对象

java.lang.reflect.Method 代表类的方法,Method对象表示某个类的方法

java.lang.reflect.Field 代表类的成员变量,Field对象表示某个类的成员变量

java.lang.reflect.Constructor 代表类的构造方法,Constructor对象表示某个类的构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void main(String[] args) throws Exception {
//使用反射解决
//加载类,返回Class类型的对象cls
Class cls=Class.forName("类的全限定名");
//通过cls获取对象的实例,无参构造器创建
Object o=cls.newInstance();
System.out.println(o.getClass());
//获取方法对象,将方法看做对象(万物皆对象)
//只能获取public修饰的方法
Method method1=cls.getMethod("方法名");
//通过method1调用方法
System.out.println("=======");
//传统:对象.方法(),反射机制 方法.invoke()
method1.invoke(o);

//Field对象表示表示类的属性
//getField只能获取public修饰的属性
Field nameField=cls.getField("属性名");
System.out.println(nameField.get(o));

//Constructor对象表示构造器,只能获取public修饰的构造器
//若无指定类型,获取无参构造器对象
Constructor constructor=cls.getConstructor();
System.out.println(constructor);

//获取有参构造器对象
Constructor constructor1=cls.getConstructor(String.class);
System.out.println(constructor1);

}

1.3 反射优点和缺点

优点:可以动态的创建和使用对象,使用领灵活,没有反射机制,框架技术失去底层支撑

缺点:使用反射基本是解释执行,对执行速度有影响

1.4 反射调用优化

Method、Field、Constructor对象都有setAccessible()方法,其作用是启动和禁用访问安全检查的开关,参数为true表示反射的对象在使用时取消访问检查,提高反射效率。参数值为false表示反射的对象执行访问检查。

二、Class类

Class类图

  1. 如图,Class也是类,继承Object类
  2. Class类对象不是new出来的,而是系统创建的
  3. 对于某个类的Class类对象,在内存中只有一份,类只加载一次
  4. 每个类的实例都会记得自己由哪个Class实例所生成的
  5. 通过Class的一系列API可以完整的得到一个类的完整结构
  6. Class对象存放在堆中
  7. 类的字节码二进制数据,放在方法区的,有的地方称为类的元数据(包含方法代码、变量名、方法名、访问权限等)

2.1 Class类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//需要通过反射创建的类的全限定名
String classFullPath = "base.reflection.pojo.Cat";
//获取Cat类对应的Class对象
Class cls = Class.forName(classFullPath);
//显示cls对象是哪个类的Class对象
System.out.println(cls);
//输出cls运行类型
System.out.println(cls.getClass());
//得到包名
System.out.println(cls.getPackage().getName());
//得到全类名
System.out.println(cls.getName());
//通过cls创建对象实例
Cat cat = (Cat) cls.newInstance();
System.out.println(cat);
//获取对象属性
Field age = cls.getField("age");
System.out.println(age.get(cat));
//通过反射给属性赋值
age.set(cat, 20);
//获取所有public修饰的属性
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field);
}
}

2.2 获取Class类对象的方法

  1. 已知一个类的全类名,而且类在类路径下,可通过Class的静态方法forName()获取。应用场景:多用于配置文件、读取类的全路径,加载类
1
Class cls = Class.forName("类的全路径");
  1. 已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高。应用场景:多用于参数传递
1
Class cls2=Cat.class;
  1. 已知某个类的实例,调用该实例的getClass()方法获取Class对象。应用场景:通过创建好的对象,获取Class对象
1
Class cls3=cat.getClass();
  1. 通过类加载器创建
1
2
ClassLoader cl=对象.getClass().getClassLoader();
Class cls4=cl.loadClass("类的全类名");
  1. 基本数据类型(八种)
1
Class cls=基本数据类型.class
  1. 基本数据类型对应的包装类,可以通过.TYPE得到Class对象(获取的是基本数据类型的Class对象)
1
Class cls=包装类.TYPE

相同的基本类型和包装类.TYPE获取的Class类对象是同一个

2.3 具有Class对象的类型

  1. 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  2. interface:接口
  3. 数组
  4. enum:枚举
  5. annotation:注解
  6. 基本数据类型
  7. void

三、类加载

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载

  1. 静态加载:编译时加载相关的类,如果不存在则报错,依赖性太强
  2. 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性

类的加载时机

  • 当创建对象时(静态加载)
  • 当子类被加载时,父类也加载(静态加载)
  • 调用类中的静态成员时(静态加载)
  • 通过反射(动态加载)

类的加载过程

java源码编译为字节码文件进行类的加载(类的加载分为三个阶段,如图),类加载后内存布局情况:类的字节码(二进制数据)存放于方法区,类的Class对象存放于堆区。

类的加载过程

3.1 加载阶段

JVMZ在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象

3.2 连接阶段

验证

  1. 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
  2. 包括:文件格式验证、元数据验证、字节码验证和符号引用验证
  3. 可以使用-Xverify:none参数关闭大部分的类验证措施,缩短虚拟机加载的时间

准备

JVM会在该阶段对静态变量,分配内存并默认初始化。这些变量所使用的内存都将在方法去中进行分配

解析

虚拟机将常量池内的符号引用替换为直接引用的过程

3.3 初始化

  1. 到初始化阶段才真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程
  2. ()方法由编译器按语句在源文件中出现的顺序,依次自动收集类中所有的静态变量的赋值动作和静态代码块中的语句,并进行合并。
  3. 虚拟机会保证一个类的()方法在多线程环境中被正确的加锁,同步。如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。

四、反射相关方法的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Car {
public Integer id;
private String address;

public Car() {
}

public Car(Integer id) {
this.id = id;
}

private Car(Integer id, String address) {
this.id = id;
this.address = address;
}

public void start(Integer id){
System.out.println("编号为:"+id+"的汽车开始运行");
}

private void restart(String address){
System.out.println("原产地为:"+address+"的汽车重启");
}

@Override
public String toString() {
return "Car{" +
"id=" + id +
", address='" + address + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static void main(String[] args) throws Exception {
Class cls=Class.forName("base.reflection.pojo.Car");

//无参构造获取car实例
Object o=cls.newInstance();
System.out.println(o);
System.out.println(Integer.class);
//通过public修饰的构造器创建对象
Constructor constructor=cls.getConstructor(Integer.class);
Object o1=constructor.newInstance(126);
System.out.println(o1);
//通过private修饰的构造器创建对象
Constructor constructor1=cls.getDeclaredConstructor(Integer.class,String.class);
//未设置为true,则不能修改或使用私有属性和方法(下同)
constructor1.setAccessible(true);
Object o2=constructor1.newInstance(100,"新西兰");
System.out.println(o2);


//修改o的public修饰的属性
Field fieldId=cls.getField("id");
fieldId.set(o,123);
//修改o的private修饰的属性
Field fieldAddress=cls.getDeclaredField("address");
fieldAddress.setAccessible(true);
fieldAddress.set(o,"冰岛");
System.out.println(o);

//调用o的方法
Method methodStart=cls.getMethod("start",Integer.class);
methodStart.invoke(o,6);
//调用o的private的方法
Method methodRestart=cls.getDeclaredMethod("restart",String.class);
methodRestart.setAccessible(true);
methodRestart.invoke(o,"英国");
}

视频资料【韩顺平讲Java】Java反射专题


反射
https://pursuemilk.github.io/2022/10/30/反射/
作者
PursueMilk
发布于
2022年10月30日
许可协议