了解java的反射机制
java的反射机制是一种在运行时动态获取类的信息并对其方法和属性进行操作的能力,通过反射,程序可以在运行时加载类,获取类的结构(如字段,方法,构造函数等),创建对象,调用方法,修改属性值等。
以下是反射机制的几个关键点:
- 获取类的 Class 对象:
- 可以通过 Class.forName(“类的全限定名”) 获取类的 Class 对象。
- 也可以通过 类名.class 或者 对象.getClass() 获取。
- 创建实例:
- 使用 Class 对象的 newInstance() 方法,可以创建类的实例(要求类有无参构造方法)。
- 也可以通过 Constructor 对象的 newInstance() 方法来创建实例,支持带参数的构造方法。
- 获取类的成员信息:
- 使用 Class 对象可以获取类的字段(Field)、方法(Method)、构造函数(Constructor)等。
- 可以通过 getDeclaredFields()、getDeclaredMethods()、getDeclaredConstructors() 获取类的所有成员(包括私有成员)。
- 操作类的成员:
- 可以使用 Field 对象的 set() 和 get() 方法修改或访问字段的值。
- 可以使用 Method 对象的 invoke() 方法调用方法。
- 修改访问权限:
- 反射允许操作私有成员,通过 setAccessible(true) 可以绕过 Java 的访问控制机制。
使用反射的典型场景包括:框架设计(如 Spring)、对象序列化与反序列化、运行时动态代理等。
简而言之,分为四点:
-
在运行时判断任意一个对象所属的类;
-
在运行时创建任意一个类的对象;
-
在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
-
在运行时调用任意一个对象的方法。
不过,反射在带来灵活性的同时,也可能带来性能开销和安全风险,因此在性能敏感的场景下使用反射时需要谨慎。
一、第一步获取Class
所有的类都是Class的示例,而所有的类都继承自Object类,所以Class也继承自Object。
基于这个理念,我们可以得到获取Class的几个核心概念:
- 类是运行时的第一公民:
在 Java 中,类不仅仅是代码中的一个定义,它在运行时也是一个独立的对象。每个类在 JVM 中都有一个 Class 对象表示它,这个对象包含了类的所有结构信息,如字段、方法、构造函数、注解等。
- 运行时动态性:
反射允许在运行时获取类的 Class 对象,从而可以动态地了解和操作这个类的内容。这意味着程序在编译时不需要知道所有类型的信息,而是可以在运行时决定要操作的类。这种动态性是反射机制的核心思想。
- 通过 Class 对象操作类:
一旦获取了某个类的 Class 对象,程序就可以通过这个对象执行各种操作,比如创建实例、访问字段、调用方法等。Class 对象是反射机制的入口,所有的反射操作都从 Class 对象开始。
-
获取 Class 对象的方式:
- 静态获取:
– 通过 类名.class 直接获取。例如,String.class。 - 通过对象获取:
– 通过对象的 getClass() 方法获取。例如,myObject.getClass()。 - 动态获取:
– 通过 Class.forName(“类的全限定名”) 方法按名称动态加载类。例如,Class.forName(“java.lang.String”)。
- 静态获取:
-
反射与类型擦除的结合:
在泛型中,由于类型擦除,泛型类型信息在编译时会被擦除。通过反射机制,程序可以在运行时重新获取并操作类型信息,从而弥补类型擦除的影响。
- 与封装性的关系:
反射机制允许程序绕过类的封装性,访问类的私有成员。这打破了传统面向对象编程中的封装原则,因此在设计 API 或使用反射时,需要小心考虑安全性和封装性。
通过这些思想,反射提供了强大的动态操作能力,使得 Java 在运行时可以灵活处理和操作对象及类的结构,但同时也带来了性能和安全上的潜在问题。
二、生成实例对象
解决这个问题之前,我们就要了解为什么要生成实例对象:
首先,生成实例对象是面向对象编程中一个基本的操作,因为对象是类的实例化,是执行程序逻辑、存储数据、调用方法的具体实体。而在 Java 的反射机制中,生成实例对象有几个关键的原因,这些原因主要与反射的动态性和灵活性有关。
- 动态操作未知类型
在一些场景中,程序在编译时并不知道具体要操作的类或对象类型。例如,在构建通用库或框架时,代码需要能够处理不同的类类型。这时可以使用反射根据类名动态生成对象实例,从而灵活地操作不同类型的对象。
- 框架设计
许多 Java 框架使用反射机制来创建对象实例,并将其注入到应用程序中。例如,依赖注入框架(如 Spring)会根据配置文件或注解,使用反射来生成类的实例,并将其注入到需要的地方。这样做使得应用程序的组件解耦,并且增强了可扩展性和灵活性。
- 处理不可访问的类和构造函数
有时候,类的构造函数是私有的,通常是为了控制实例化的过程。然而,在某些情况下,程序仍然需要创建这些类的实例。这时,反射可以通过 Constructor.setAccessible(true) 绕过 Java 的访问控制机制,生成实例对象。
生成实例对象一般有两种方式:
- 调用
Class
对象的newInstance()
方法
Person person = personClass.newInstance();
- 通过获取类的构造方法进行实例化
无参构造
Constructor<Person> constructor = personClass.getConstructor();
Person person = constructor.newInstance();
有参构造
Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
Person person = constructor.newInstance("tom", 18);
三、访问属性
访问属性
-
访问公有属性
// 获取age属性 Field ageField = personClass.getField("age"); // 获取person对象的age属性值 Object age = ageField.get(person); System.out.println(age); //将person对象的age属性值设置为20 ageField.set(person,20); System.out.println(person.age);
-
访问私有属性
// 获取私有的name属性 Field nameField = personClass.getDeclaredField("name"); // 将name属性的可访问性设置为true nameField.setAccessible(true); // 获取person对象的name属性值 Object name = nameField.get(person); System.out.println(name); // 将person对象的name属性值设置为jerry nameField.set(person,"jerry"); System.out.println(person.getName());
四、调用方法
-
访问公有方法
// 获取getName()方法 Method getName = personClass.getMethod("getName");//getMethod(方法名,参数列表) // 通过person对象调用getName()方法,并获取执行结果 Object name = getName.invoke(person); System.out.println(name); // 获取setName()方法 Method setName = personClass.getMethod("setName", String.class); // 通过person对象调用setName()方法,并传递参数 setName.invoke(person,"trump"); System.out.println(person.getName());
-
访问私有方法
// 获取私有的playGame()方法 Method playGame = personClass.getDeclaredMethod("playGame"); // 将playGame()方法的可访问性设置为true playGame.setAccessible(true); // 通过person对象调用playGame()方法 playGame.invoke(person);