这篇文章将详细介绍 Java 的反射机制 |
什么是反射?为什么要使用反射?
Java 反射机制是在运行状态中:
- 对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取类的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。通过使用反射我们可以很方便地动态创建对象、动态调用对象的方法和访问对象的成员变量。
反射更多的是应用在各类 Java 框架之中,如果想要深入了解框架知识,就必须扎实地掌握 Java 的反射机制。 |
反射机制的原理
我们先来了解一下与反射机制相关的类:
类名 | 用途 |
---|---|
java.lang.Class | 描述其他类的类 |
java.lang.reflect.Method | 描述类的方法 |
java.lang.reflect.Field | 描述类的成员变量 |
java.lang.reflect.Constructor | 描述类的构造方法 |
我们可以简单地理解为:Java 的反射机制是主要是通过操作这四个类来实现的。其中 Class 类是最根本、最核心的类,因为另外三个相关的类都是通过 Class 类的实例对象来获取的,换句话说,Class 类是 Java 反射的源头,反射的操作就是从创建 Class 实例对象开始的。下面我们就通过介绍 Class 类来一步步了解 Java 的反射操作。
Class 类
Class 类是用来描述其他类的类,也就是说,Class 类的每一个实例化对象就是对其他类的描述。
创建一个类所对应的 Class 对象的常用方式有:
- 调用 Class 的静态方法 forName
- 使用类的 .class 语法
- 调用对象的 getClass() 方法
比如:
public static void main(String[] args) {
try {
// 通过调用 Class 的静态方法 forName 创建 Class 对象
Class<?> strClass1 = Class.forName("java.lang.String");
// 通过使用类的 .class 语法创建 Class 对象
Class<?> strClass2 = String.class;
// 通过调用对象的 getClass() 方法创建 Class 对象
String str = "abc";
Class<?> strClass3 = str.getClass();
} catch (Exception e) {
e.printStackTrace();
}
}
由于在 Object 类中定义了方法 public final native Class<?> getClass() 并且这个方法被所有子类继承,所以,每个类的实例对象都可以通过调用 getClass() 方法来获取对应的 Class 对象。 |
有了 Class 对象之后我们就可以进行各种各样的操作了,比如创建类的实例对象,获取类的方法和属性的信息,调用对象的方法以及设置对象的属性值等等。
📝 通过 Class 动态创建类的实例对象
使用 Class 创建对象的方式有两种:
- 直接调用 Class 对象的 newInstance() 方法来创建类的实例对象
- 通过 Class 获取类的构造器对象,然后再通过调用构造器对象的 newInstance() 方法来创建类的实例对象
比如:
public static void main(String[] args) {
try {
// 通过调用 Class 的静态方法 forName 创建 Class 对象
Class<?> strClass1 = Class.forName("java.lang.String");
// 通过调用 newInstance() 创建对象:已弃用
String str1 = (String)strClass1.newInstance();
// 先通过 Class 获取类的构造器对象,然后再调用构造器对象的 newInstance() 方法
Constructor<?> strCon = strClass1.getConstructor();
String str2 = (String)strCon.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
这里需要注意的是,第一种方式在新版 JDK 中已经弃用,不建议使用了,我们推荐第二种方式来动态创建对象。在第二种方式中,获取类的构造函数有四种方式:
- getConstructor(Class<?>… parameterTypes):获取指定参数的公有构造方法
- getDeclaredConstructor(Class<?>… parameterTypes):获取指定参数的构造方法
- getConstructors():获取所有的公有构造方法
- getDeclaredConstructors():获取所有构造方法
这里唯一需要区分的是:没有 declared 所获取的构造方法只能是公有的,而有 declared 所获取的是构造方法不限于公有的,私有的、保护的都可以。如果获取的是私有构造方法,则需要调用构造器的 setAccessible(true) 来设置访问属性为 true,这样才有权限调用 newInstance() 方法。
获取指定参数的构造方法提供的参数类型使用类的 .class 语法,个数是可选的,比如: getConstructor(String.class) 表示获取的构造方法包含一个 String 类型的参数,在实例化对象时就需要这样调用:newInstance(“hello”),类似的,getConstructor(String.class, String.class) 就需要这样调用:newInstance(“hello”, “Java”)。
📝 通过 Class 调用对象的方法
通过 Class 调用对象的方法的步骤为:
1、获取 Class 对象
2、调用 Class 的 getMethod() 方法获取一个 Method 对象
3、通过 Method 的 invoke() 方法调用对象的方法
比如:
public class Student {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public static void main(String[] args) {
try {
// 获取 Class 对象
Student stu = new Student();
Class<?> stuCls = stu.getClass();
// 通过 Class 获取 Student 类的 setName 方法
Method method = stuCls.getMethod("setName", String.class);
// 调用 setName 方法
method.invoke(stu, "Jack");
System.out.println("Hello: " + stu.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
>>>>>
Hello: Jack
同样,获取 Method 对象也有四种方式:
- getMethod(String name, Class<?>… parameterTypes):获取指定参数的公有方法
- getDeclaredMethod(String name, Class<?>… parameterTypes):获取指定参数的方法
- getMethods():获取所有公有方法
- getDeclaredMethods():获取所有方法
这四种方式的区分方法和构造器一致:没有 Declare 只能获取公有的方法,而有 Declare 可以获取包括公有、私有等权限的方法。获取指定的方法第一个参数指定需要获取的方法名,后面的可选参数类型就是指定方法的参数类型和个数。如果获取的是私有方法,则需要设置访问属性为 true,比如:method.setAccessible(true),否则无法调用方法
我们注意到,在调用 Method 的 invoke 方法时,和我们平时调用方法的方式有点区别,除了需要提供调用方法的参数之外,还需提供调用这个方法所属的对象。
为什么要提供调用方法所属的对象呢?
原因是这样子的:Java 运行的时候,某个类无论生成多少个对象,他们都会对应同一个 Class 对象,它表示正在运行程序中的类和接口,所以在使用 invoke 方法时就需要明确指定到底调用的是哪个对象的方法。
📝 通过 Class 访问和设置对象属性
通过 Class 访问和设置对象属性的步骤为:
1、获取 Class 对象
2、调用 Class 的 getField() 方法获取 Field 对象
3、通过 Field 对象的 get() 方法获取属性对象,通过 set() 方法修改对象的属性
比如:
public class Student {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public static void main(String[] args) {
try {
// 获取 Class 对象
Student stu = new Student("Jack");
Class<?> stuCls = stu.getClass();
// 通过 Class 获取 Field 对象
Field field = stuCls.getDeclaredField("name");
field.setAccessible(true);
// 访问属性对象
String name = (String) field.get(stu);
System.out.println("Hello: " + name);
// 设置属性对象
field.set(stu, "Marry");
System.out.println("Hello: " + stu.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
>>>>>
Hello: Jack
Hello: Marry
和获取 Method 对象一样,获取 Filed 也有四种方法:
- getField(String name):获取指定的公有属性
- getDeclaredField(String name):获取指定的属性
- getFields():获取所有公有属性
- getDeclaredFields():获取所有属性
区别和用途参考 Method,例子中因为属性 name 是 private 类型的,所以只能使用 getDeclaredField(),并且需要调用 setAccessible(true) 来设置属性的可访问性为 true。
这里需要注意的是:通过 Field 的 get() 方法返回的属性对象只是一个新拷贝的属性对象,修改它的值也只是修改临时变量的值,不会影响任何 Student 对象的 name 属性值。 和 Method 的 invoke() 方法一样,Field 的 set() 方法也需要指定设置的是哪个 Student 对象的 name 属性值。
总结
目前为止我们已经介绍完了,Java 反射的基本原理和用法,说白了就是在 Java 程序运行过程中,动态地创建类的实例对象、调用类实例对象的方法、访问和修改对象的属性。通过反射我们可以很清楚的知道一个类的内部结构(包含哪些方法和属性)。并且我们也知道了,不管一个类有多少个实例对象,都只能有一个对应的 Class 对象,所以通过 Class 对象获取的 Method、Field 对象在调用 invoke、set 方法时需要提供调用的对象。
Java 的反射机制就是通过操作 Class 类、Method 类、Filed 类和 Constructor 类这四个的类的各个方法来实现的,本篇文章只列出了少量常用的方法,如果想要了解更多的话,可以查看它们的其它方法。