JAVA反射机制详解
反射概述
- Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象后,再通过class对象进行反编译,从而获取对象的各种信息。
- Java属于先编译后运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能要动态的加载某些类,这些类因为之前用不到,所以没有被加载到JVM,这时就可以通过反射,在运行时动态加载类,创建对象并调用其属性,不需要提前在编译期知道运行对象是谁。
反射可以提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象并获取成员变量和方法
- 在运行时判断任意一个类所具有的的成员变量和方法
- 在运行时获取泛型的信息
- 在运行时处理注解
- 生成动态代理
反射相关的API
- java.lang.Class;代表一个类
- java.lang.reflect.Method;代表类的方法
- java.lang.reflect.Field;代表类的成员变量
- java.lang.reflect.Constructor;代表类的构造器
- …
获取Class类的实例的方法
public void test3() throws ClassNotFoundException {
// 1. 调用运行时类的属性 .class
Class<Person> clazz1 = Person.class;
// 2. 通过运行时类的对象
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//3. 通过全类名的方式,调用Class的静态方法:forName(String classPath) 最常用
Class<?> clazz3 = Class.forName("org.example.Person");
/*clazz3 = Class.forName("java.lang.String");*/
System.out.println(clazz3);
System.out.println(clazz1 == clazz2);
System.out.println(clazz3 == clazz1);
// 4. 使用类的加载器:ClassLoader
ClassLoader classLoader= ReflectionTest.class.getClassLoader();
Class<?> clazz4 = classLoader.loadClass("org.example.Person");
System.out.println(clazz4);
System.out.println(clazz3==clazz4);
}
反射的应用场景
运行时加载类
编译的时候无法确定造哪一个类的对象
举个例子:在前后端交互过程中,后端先启动并部署到服务器上,当前端发送某些请求时,后端会动态创建对应的对象来执行相应的操作。如:前端发送一个\login请求,后端就会动态创建一个login对象,前端发送一个\register请求,后端就会动态创建一个register对象。像这种情况,在编译时是无法确定对象类型的,只有在运行时才能够确定,这个时候就需要用到反射机制。
封装和反射相矛盾?
封装性体现的是建议去调用共有方法,而反射解决的问题是能不能调用的问题。
那些类型可以有Class对象?
- class; 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface; 接口
- []; 数组
- enum; 枚举
- annotation; 注解@interface
- primitive type; 基本数据类型
- void;
类的加载过程
当程序主动使用某个类时,如果该类还没有被加载到内存中,系统会通过如下三个步骤完成初始化。
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(引用地址),所有需要访问和使用类数据只能通过这个Class对象。加载过程需要类加载器参与。
- 链接:将JAVA类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范
- 准备:正式为类变量(Static)分配内存并设置类变量默认初始值的阶段,这些内存豆浆在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器()方法的过程,类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,若发现其父类还没有被初始化,则需先触发其父类的初始化。
- 虚拟机会保证一个类的类构造器()方法在多线程环境中被正确加锁和同步。
例如:
public class ClassLoadingTest{
public static void main(String[] args) {
System.out.println(A.m);
}
}
class A{
static {
m=10;
}
static int m=100;
}
/*
* 1. 加载,首先把A加载到方法区,并创建一个class对象指向该方法区
* 2, 链接,为m赋值0;
* 2. 初始化,先为m赋值10,再赋值100;
*
* */