反射机制主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。其中LEAD/LEAD++ 、OpenC++ 、MetaXa和OpenJava等就是基于反射机制的语言。适用于计算机科学领域关于应用反射性的研究。本文简单整理了一些关于反射的相关问题。

原文链接:全网最完整Java学习笔记

基本介绍

反射:在程序运行期间动态地获取类的信息并对类进行操作的机制。在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性

通过反射机制可以实现:

  • 获取类或对象的Class对象:程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的所有方法和属性(包括私有,私有需要给该字段调用setAccessible(true)方法开启私有权限)。注意类的class对象是运行时生成的,类的class字节码文件是编译时生成的。

  • 创建实例:程序运行时,可以利用反射先创建类的Class对象再创建该类的实例,并访问该实例的成员;Xxx.class.newInstance() ;例如在Spring容器类源码里,Bean实例化就是通过Bean类的Class对象。Bean类的Class对象是从BeanDefinition对象的type成员变量取的。BeanDefinition对象存储一些Bean的类型、名称、作用域等声明信息。

  • 生成动态代理类或对象:程序运行时,可以通过反射机制生成一个类的动态代理类或动态代理对象。例如JDK中Proxy类的newProxyInstance静态方法,可以通过它创建基于接口的动态代理对象。

类的字节码文件和Class对象的区别:

  • 类的class字节码文件是编译时生成的,类的class对象是运行时生成的。
  • 类的字节码文件是存储在电脑硬盘中的文件,如Test.class;类的Class对象是存放在内存中的数据,可快速获取其中的信息;
  • 两者都存储类的各种信息;

获取类Class对象的JVM底层:如果该类没有被加载过,会首先通过JVM实现类的加载过程,即加载、链接(验证、准备、解析)、初始化,加载阶段会生成类的Class对象。

获取类Class对象的方法:dog.getClass();Dog.class;Class.forName("package1.Dog");

特点:

  • 访问私有成员:构造方法、成员变量、方法对象取消访问检查可以访问私有成员;public void setAccessible(boolean flag):值为true,取消访问检查
  • 越过泛型检查:反射可以越过泛型检查,例如在ArrayList中添加字符串

反射的优缺点:

  • 优点
    • 运行时获取属性:运行期间能够动态的获取类,提高代码的灵活性。
    • 访问私有成员:构造方法、成员变量、方法对象取消访问检查可以访问私有成员;public void setAccessible(boolean flag):值为true,取消访问检查
    • 越过泛型检查:反射可以越过泛型检查,例如在ArrayList中添加字符串
  • 缺点:性能差。性能比直接的Java代码要差很多。

应用场景:

  • JDBC加载数据库的驱动:使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序;
  • Bean的生命周期:
    • 实例化xml解析出的类:多数框架都支持注解或XML配置来定义应用程序中的类,从xml配置中解析出来的类是字符串,需要利用反射机制实例化;如Spring通过<bean id="xx" class="类全限定名"><property name="按名称注入" ref="被注入Bean的id">定义bean,然后通过Class.forName("xxx.Xxx")获取类的class对象,然后创建实例。
    • 注解容器类加载Bean、实例化Bean:Bean的生命周期中,注解容器类的构造方法会遍历@ComponentScan("扫描路径")下的.class文件,通过类加载器.load("类名")方式获得类的class对象,存入beanDefinitionMap。然后遍历beanDefinitionMap,通过class对象实例化等。
  • AOP创建动态代理对象:面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理对象,这必须由反射机制来实现。

验证反射可以绕过泛型检查:

基于反射,我们可以给ArrayList<Integer>对象中,加入字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {

public static void main(String[] args) throws NoSuchMethodException,
InvocationTargetException, IllegalAccessException {
ArrayList<Integer> integers = new ArrayList<>();
Class<? extends ArrayList> aClass = integers.getClass();
Method add = aClass.getMethod("add", Object.class);
add.invoke(integers, 1);
add.invoke(integers, 2);
add.invoke(integers, 3);
add.invoke(integers, 4);
add.invoke(integers, "hello");
System.out.println(integers);
}
}
// [1,2,3,4,hello]

反射获取Class对象

基本介绍

Class类的对象:程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的所有方法和属性(包括私有,私有需要给该字段调用setAccessible(true)方法开启私有权限)。

注意类的class对象是运行时生成的,类的class字节码文件是编译时生成的。

获取类Class对象:

  • 对象.getClass():Object是一切类的根类,Object类有个getClass()方法可以获取类的Class对象。例如dog.getClass();
  • 类名.class(推荐):例如Dog.class;
  • Class.forName(“类名”):例如Class.forName(“package1.Dog”);

Class对象的常用方法:

  • 获取类的信息
    • String getName():返回类的全限定名。全限定名包含包名和类名,用于唯一标识类或接口。例如package1.Dog、java.lang.String、java.util.Map$Entry
    • String getSimpleName():返回类的简单名。例如Dog
    • tring getCanonicalName():返回类的规范名。规范名是类的规范字符串形式,常用于打印和日志记录。例如package1.Dog、java.lang.String、java.util.Map.Entry
    • Package getPackage():返回此类所属的包。
    • ClassLoader getClassLoader():返回该类的类加载器。
    • Class<? super T> getSuperclass():返回表示类的超类的 Class 对象。
    • Class<?>[] getInterfaces():返回类实现的所有接口。
    • boolean isInterface():判断是否是接口。
    • boolean isAnnotation():判断是否是注解类型。
    • boolean isEnum():判断是否是枚举类型。
    • Annotation[] getAnnotations():返回此元素上存在的所有注解。
    • Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解。
    • T getAnnotation(Class annotationClass):返回指定类型的注解,如果该注解存在于此元素上,否则返回 null。例如Spring源码中,ApplicaitonContext构造器判断一个类是不是Bean,是通过这个方法判断类有没有@Comonent等注解,从而判断它是不是Bean。
  • 获取成员:
    • Field[] getFields():返回类的所有公共字段,包括从父类继承的字段。
    • Field[] getDeclaredFields():返回类声明的所有字段,不包括继承的字段。
    • Method[] getMethods():返回类的所有公共方法,包括从父类继承的方法。
    • Method[] getDeclaredMethods():返回类声明的所有方法,不包括继承的方法。
    • Constructor<?>[] getConstructors():返回类的所有公共构造方法。
    • Constructor<?>[] getDeclaredConstructors():返回类声明的所有构造方法。
  • 其他方法
    • T newInstance():创建此 Class 对象所表示的类的一个新实例(使用默认构造方法)。

Spring源码:Bean初始化时判断类是否Bean、判断属性是否需要填充都用到了反射

Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程-CSDN博客

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
// 狗类
public class Dog {
private int weight;
public String name;
// 其他定义
}
// 获取类的Class对象:
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//方法1:类的class属性
Class<Dog> c1=Dog.class;
//方法2:对象的getClass方法
Dog wangCaiDog = new Dog(23, "旺财");
Class<? extends Dog> c2= wangCaiDog.getClass();
//方法3:Class类的静态方法forName
Class<?> c3= Class.forName("package1.Dog");
// 方法4:使用类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
Class<?> c5 = systemClassLoader.loadClass("package1.Dog");
// 输出:class package1.Dog
System.out.println(c1);
//三种方式获取到Class对象地址是完全一致的
// 输出:true
System.out.println(c1==c2&&c1==c3);
}
}
// 获取类的信息
public class Test {
public static void main(String[] args) throws Exceptionn {
Class<? extends Dog> dogClass = Dog.class;
System.out.println(dogClass.getName());
System.out.println(dogClass.getSimpleName());
System.out.println(dogClass.getCanonicalName());
}
}

全限定名和规范名

全限定名和规范名:

外部类的全限定名和规范名是一样的,都是“xxx.类名”。区别主要在内部类,内部类的全限定名是“xxx.外部类名$内部类名”,规范名是“xxx.外部类名.内部类名”。

  • 简单名:只包含类名。例如Dog、String、Entry
  • 全限定名:包含包名和类名,用于唯一标识类或接口,通过全限定名能找到唯一一个类。例如package1.Dog、java.lang.String、java.util.Map$Entry
  • 规范名:类的规范字符串形式,常用于打印和日志记录。例如package1.Dog、java.lang.String、java.util.Map.Entry
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception {
// java.util.Map
System.out.println(Map.class.getName());
// java.util.Map
System.out.println(Map.class.getCanonicalName());
// 输出 "java.util.Map$Entry"
System.out.println(Map.Entry.class.getName());
// 输出 "java.util.Map.Entry"
System.out.println(Map.Entry.class.getCanonicalName());
}

反射获取成员

反射获取构造方法

Class对象获取构造器:

  • getConstructor(Class<?>… parameterTypes):获取指定参数类型的公共构造方法。返回值是Constructor类
  • getDeclaredConstructor(Class<?>… parameterTypes):获取指定参数类型的构造方法(包括私有构造方法)。
  • getConstructors():获取所有公共构造方法。
  • getDeclaredConstructors():获取所有构造方法(包括私有构造方法)。
  • **newInstance()**:创建类的新实例。
    • Class类的newInstance():只能够调用无参构造函数;
    • Constructor类的newInstance():可以根据传入的参数,调用任意构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 获取所有构造器对象:
public static void main(String[] args) {
Class<Dog> dogClass = Dog.class;
Constructor<?>[] cons = dogClass.getDeclaredConstructors();
for(Constructor<?> con:cons){
System.out.println(con);
}
}
// 获取单个构造器并实例化:
public class Test {
public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
Class<Dog> dogClass=Dog.class;
//获取单个构造方法对象
Constructor<?> con=dogClass.getDeclaredConstructor();
//构造方法对象实例化,会调用无参构造方法
Object dogObject = con.newInstance();
// 无参构造器实例化,也可以直接用Class对象的newInstance方法,带参就不行了
Dog dog = dogClass.newInstance();
//重写了Dog类的to_String,所以输出:Dog{weight=0, name='null'}
System.out.println(dogObject);
System.out.println(dog);
}
}

反射获取字段

Class对象获取字段:

  • getField(String name):返回指定名称的公共字段。返回类型是字段类Field。
  • getDeclaredField(String name):返回指定名称的字段(包括私有字段)
  • getFields():返回所有公共字段。
  • getDeclaredFields():返回所有字段(包括私有字段)

字段类Field常用方法:

获取字段信息

  • getName():返回字段的名称。
  • getType():返回字段的类型。
  • getModifiers():返回字段的修饰符。
  • getDeclaringClass():返回声明该字段的类的 Class 对象。

获取和设置字段值

  • get(Object obj):返回指定对象上此字段的值。
  • getBoolean(Object obj):返回指定对象上此字段的值(如果字段类型是 boolean)。
  • getByte(Object obj):返回指定对象上此字段的值(如果字段类型是 byte)。
  • getChar(Object obj):返回指定对象上此字段的值(如果字段类型是 char)。
  • getDouble(Object obj):返回指定对象上此字段的值(如果字段类型是 double)。
  • getFloat(Object obj):返回指定对象上此字段的值(如果字段类型是 float)。
  • getInt(Object obj):返回指定对象上此字段的值(如果字段类型是 int)。
  • getLong(Object obj):返回指定对象上此字段的值(如果字段类型是 long)。
  • getShort(Object obj):返回指定对象上此字段的值(如果字段类型是 short)。
  • set(Object obj, Object value):设置指定对象上此字段的值。注意私有字段默认不允许赋值,要赋值必须给私有字段setAccessible(true)。
  • setBoolean(Object obj, boolean value):设置指定对象上此字段的值(如果字段类型是 boolean)。
  • setByte(Object obj, byte value):设置指定对象上此字段的值(如果字段类型是 byte)。
  • setChar(Object obj, char value):设置指定对象上此字段的值(如果字段类型是 char)。
  • setDouble(Object obj, double value):设置指定对象上此字段的值(如果字段类型是 double)。
  • setFloat(Object obj, float value):设置指定对象上此字段的值(如果字段类型是 float)。
  • setInt(Object obj, int value):设置指定对象上此字段的值(如果字段类型是 int)。
  • setLong(Object obj, long value):设置指定对象上此字段的值(如果字段类型是 long)。
  • setShort(Object obj, short value):设置指定对象上此字段的值(如果字段类型是 short)。

其他方法

  • isAccessible():返回字段是否可访问。
  • setAccessible(boolean flag):设置字段的可访问性。通过这个方法可以让私有字段也可以赋值。
  • oGenericString():返回字段的描述,包括泛型信息。
  • getAnnotatedType():返回此字段的带注释的类型。
  • getAnnotations():返回字段的所有注解。
  • getAnnotation(Class annotationClass):返回字段的指定类型的注解,如果该注解不存在,则返回 null。例如Spring源码中依赖注入这一块,就是基于反射获取类中字段有没有@Resource、@Component等注解,有的话就是要注入Bean.
  • getDeclaredAnnotations():返回直接存在于此字段上的所有注解。
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
// 获取成员变量对象并赋值
public class Test {
public static void main(String[] args) throws NoSuchFieldException, InstantiationException, IllegalAccessException {
// 1.获取Class对象,并实例化
Class<? extends Dog> dogClass = Dog.class;
Dog dog = dogClass.newInstance();
// 2.获取字段对象
Field nameField = dogClass.getDeclaredField("name");
Field weightField = dogClass.getDeclaredField("weight");
// 3.给字段对象赋值
nameField.set(dog, "旺财");
// 注意私有字段默认不允许赋值,要赋值必须给私有字段设置可访问
weightField.setAccessible(true);
weightField.set(dog, 10);
System.out.println(dog);
}
}
// 通过Field获取的Class类:
public class Test {
public static void main(String[] args) throws NoSuchFieldException {
Class<Dog> dogClass=Dog.class;
Field weightField = dogClass.getDeclaredField("name");
Class<?> dogClassByField = weightField.getDeclaringClass();
// 通过字段获取到的class对象和源class对象是地址是一样的,事实上一个类的所有Class对象都是一个实例
// true
System.out.println(dogClassByField==dogClass);
}
}

反射获取普通方法

Class对象获取成员方法的方法:

  • getMethod(String name, Class<?>… parameterTypes):返回指定名称和参数类型的公共方法。返回值是方法类Method。

  • getDeclaredMethod(String name, Class<?>… parameterTypes):返回指定名称和参数类型的方法(包括私有方法)。

  • getMethods():返回所有公共方法(包括从父类继承的方法)。

  • getDeclaredMethods():返回所有方法(包括私有方法)。

  • Method类的方法:

    • 获取方法信息
      • getName():返回方法的名称。
      • getReturnType():返回方法的返回类型。
      • getParameterTypes():返回方法参数类型的数组。
      • getModifiers():返回方法的修饰符。
      • getDeclaringClass():返回声明此方法的类的 Class 对象。
  • 调用方法

    • Object invoke(Object obj, Object… args):调用指定对象上此 Method 对象表示的基础方法。
  • 其他方法

    • isAccessible():返回方法是否可访问。
    • setAccessible(boolean flag):设置方法的可访问性。
    • getAnnotations():返回此方法的所有注解。例如Spring源码中通过此方法判断一个类中
    • isAnnotationPresent(Class<? extends Annotation> annotationClass):判断此方法是否被指定的注解类型注释。
    • getAnnotation(Class annotationClass):返回该方法的指定类型的注解。
    • getExceptionTypes():返回此方法抛出的异常类型的数组。
    • toGenericString():返回方法的描述,包括泛型信息。

获取成员变量对象并调用:

1
2
3
4
5
6
7
8
9
10
11
12
// 1.获取构造方法对象并实例化
Class<?> c= Class.forName("train.Dog");
Constructor<?> con=c.getConstructor();
Object obj = con.newInstance();
// 2.获取成员方法对象
Method eat = c.getMethod("eat");
// 3.通过成员方法对象的invoke方法,调用构造方法对象的成员方法
// 无参无返回值方法
eat.invoke(obj);
// 带参有返回值方法
Object sucess= eat.invoke(obj,"food");
System.out.println((boolean)sucess);

相关问题

什么是 Java 序列化?什么情况下需要序列化?

Java 序列化是为了保存各种对象在内存中的状态,并且可以把保存的对象状态再读出来
以下情况需要使用 Java 序列化:

  • 想把内存中的对象状态保存到一个文件中或者数据库中时候;
  • 想用套接字在网络上传送对象的时候;
  • 想通过RMI(远程方法调用)传输对象的时候。

动态代理是什么?有哪些应用?实现原理?

动态代理:运行时动态创建代理对象的技术,通过反射机制生成代理类,无需手动编写代理代码即可实现对目标对象的间接访问和控制。

动态代理应用:实现AOP、日志记录、权限校验、hibernate 数据查询、测试框架的后端 mock、rpc等功能

实现原理:包括JDK动态代理(基于接口)和CGLIB动态代理(基于类继承)