反射
约 1733 字大约 6 分钟
2025-01-15
1.概述
1.1 什么是反射
- Java反射机制指的是在Java程序运行状态中,对于任何一个类,都可以获得这个类的所有属性和方法;
- 对于给定的一个对象,都能够调用它的任意一个属性和方法。
1.2 为什么使用反射
在Java编程语言中,反射是一种强有力的工具,是面向抽象编程的一种实现方式,它能使代码语句更加灵活,极大提高代码的运行时装配能力。Java反射机制允许编程人员在对类未知的情况下,获取类相关信息的方式变得更加多样灵活,调用类中相应方法,是Java增加其灵活性与动态性的一种机制。
总结一下,Java反射机制有如下作用:
- 反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力;
- 通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类;
- 使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法;
- 反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。
2.反射的原理
- 首先我们需要了解Java程序运行的过程,该过程包含两个阶段:编译和运行。
- 在程序编译阶段,Java代码会通过JDK编译成 .class字节码文件;
- 在程序运行阶段,JVM会去调用业务逻辑对应需要的的字节码文件,生成对应的Class对象,并调用其中的属性方法完成业务逻辑。
- Java的反射机制原理:在程序运行阶段,主动让JVM去加载某个 .class文件生成Class对象,并调用其中的方法和属性。 如下图:
3.反射的使用
四个常用的类
类名 | 作用 |
---|---|
Class类 | 类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 类的成员变量(成员变量也称为类的属性) |
Method类 | 类的方法 |
Constructor类 | 类的构造方法 |
3.1 获取类对象
3.1.1 具体类的类名的class属性
Class bookClass = Book.class;
//输出类名
System.out.println(bookClass.getName());
3.1.2 对象实例的getClass方法
Book book = new Book();
Class bookClass = book.getClass();
//输出类名
System.out.println(bookClass.getName());
3.1.3 class.forName(类的全路径)
Class bookClass;
try {
bookClass = Class.forName("test.Book");
//输出类名
System.out.println(bookClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
3.1.4 类加载器的loadClass(类的全路径)
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");
3.2 获取类对象的构造函数
- 当获取到一个类的Class对象之后,可以调用Class对象的getDeclaredConstructors()方法获取该类的构造函数,如下:
// 反射所有声明的构造方法
public static void reflectAllConstructor() {
System.out.println(TAG + "=============获取所有的声明的构造函数==============");
try {
Class<?> classBook = Class.forName("test.Book");
Constructor<?>[] constructorsBook = classBook
.getDeclaredConstructors();
for (Constructor constructor : constructorsBook) {
System.out.println(TAG + constructor);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
- 获取了构造函数之后,调用Constructor类对象的newInstance()即可构造出我们想要类的对象,如下:
Book book = (Book)constructor.newInstance();
3.3 获取类对象的方法
- 当我们得到了一个Class对象之后,我们可以获取该类的所有方法,如下:
- getDeclaredMethods()和getMethods()都可以获取到类的方法,辨别?
- getMethods()获取了自己定义的公用方法(private获取不了),还把Object父类的公用方法也获取了
- getDeclaredMethods()只能获取自己类中定义的方法,但是可以获取到private方法
// 反射所有的public的函数
public static void reflectPublicMethods() {
System.out.println(TAG + "=============获取所有的public的函数==============");
try {
Class<?> classBook = Class.forName("test.Book");
Method[] methodsBook = classBook.getMethods();
for (Method method : methodsBook) {
System.out.println(TAG + method);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射所有的声明的方法
public static void reflectAllMethods() {
System.out.println(TAG + "=============获取所有的声明的函数==============");
try {
Class<?> classBook = Class.forName("test.Book");
Method[] methodsBook = classBook.getDeclaredMethods();
for (Method method : methodsBook) {
System.out.println(TAG + method);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
3.4 获取类对象的属性
当我们得到了一个Class对象之后,我们可以获取该类的所有属性,代码如下:
同Methods,获取属性也有getDeclaredFields()和getFields()两种。
// 反射所有的public的属性
public static void reflectPublicFields() {
System.out.println(TAG + "=============获取所有的public的属性==============");
try {
Class<?> classBook = Class.forName("test.Book");
Field[] fieldsBook = classBook.getFields();
for (Field field : fieldsBook) {
System.out.println(TAG + field);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射所有的声明的属性
public static void reflectAllFields() {
System.out.println(TAG + "=============获取所有的声明的属性==============");
try {
Class<?> classBook = Class.forName("test.Book");
Field[] fieldsBook = classBook.getDeclaredFields();
for (Field field : fieldsBook) {
System.out.println(TAG + field);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
4.反射的优缺点
- 优点:让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
- 缺点:
- 同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。
- 另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
6.动态代理和静态代理区别
- 静态代理是指在编译时就已经确定了代理对象和目标对象的关系,代理类和目标类都需要实现相同的接口,代理类持有目标对象,并在方法调用前后进行额外的操作
- 动态代理是指在运行时生成代理对象,而无需手动编写代理类。Java的动态代理机制是基于反射实现的,通过使用Proxy类和InvocationHandler接口来实现动态代理
- 定义一个接口,作为目标接口
- 创建一个实现InvocationHandler接口的类,重写invoke方法,其中使用method.invoke执行目标方法,并实现方法增强
- 测试类:使用Proxy类的静态方法newProxyInstance()生成代理对象,同时指定目标对象和InvocationHandler,最后使用代理对象执行目标方法
- 区别:
- 灵活性:动态代理更加灵活,不需要针对每个目标类都创建一个代理类
- JVM:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件,动态代理是在运行时动态生成类字节码
7.JDK动态代理和CGLIB动态代理的区别
- JDK 动态代理只能只能代理实现了接口的类或者直接代理接口
- CGLIB 可以代理接口还可以代理未实现任何接口的类
- CGLIB 动态代理是通过继承目标类并重写其方法来创建代理对象,因此不能代理声明为 final 类型的类和方法
- 效率: JDK 动态代理更优秀