反射的概念 #
对于任意一个类,都能够得到这个类的所有属性和方法; 对于任意一个对象,都能够调用它的任意属性和方法; 这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。
对象可以通过反射获取他的类,类可以通过反射拿到所有⽅法(包括私有,比如Runtime类),且拿到的⽅法可以调⽤,总之通过“反射”,我们可以将Java这种静态语⾔附加上动态特性。
Java 中的 Class #
反射实现的关键在于 Class类。
在 Java 中定义的类(Class)本身也是一个对象,即 java.lang.Class
类的实例。
- 这个 Class 类的用处在于可以描述其他类(获取Dog类的元数据)
- 用对象记录类
- Class 对象一般称为类对象
- Class 类是 java.lang 包的一部分,而 java.lang 包是自动被所有Java应用程序导入的。这意味着你不需要显式地导入 Class 类,它已经默认可用。
理解创建一个 Class 对象 #
对其他类创建一个 Class 对象,创建的是一个描述那个其他类本身的对象,不是那个类本身所实例化的对象。
当然,可以在后续通过 newInstance 的方法实例化得到那个类的对象,需要注意 newInstance 方法返回的是 Object 类型,需要强制转换成需要的对象类型。
// 获取Dog类的元数据(dogClass这个Class对象 本质上描述了 Dog这个对象)
Class<?> dogClass = Dog.class;
// 使用Class对象创建实例(对象)
Dog newDog = (Dog) dogClass.getDeclaredConstructor().newInstance();
反射的实现 #
反射⾥极为重要的⽅法:
- 获取类: forName
- 实例化类对象: newInstance
- 获取函数: getMethod、getConstructor
- 执⾏函数: invoke
public void execute(String className, String methodName) throws Exception {
// 获取类
Class<?> clazz = Class.forName(className);
// 创建实例
Object instance = clazz.getDeclaredConstructor().newInstance();
// 在实例上调用方法
clazz.getMethod(methodName).invoke(instance);
}
通过getMethod反射执行代码 #
通过 getMethod
调用runtime类的公共静态方法 getRuntime
来获取runtime类的实例:
public class Exec {
public static void main(String[] args) throws Exception {
//java.lang.Runtime.getRuntime().exec("calc");
Class runtimeClass = Class.forName("java.lang.Runtime");
// getRuntime是静态方法,invoke时不需要传入对象
Object runtime = runtimeClass.getMethod("getRuntime").invoke(null);
runtimeClass.getMethod("exec", String.class).invoke(runtime,"open /System/Applications/Calculator.app");
}
}
getConstructor 方法 #
如果一个类没有无参构造方法,也没有类似
getRuntime
的公共静态方法,我们怎样通过反射实例化该类呢?使用 getConstructor 获取指定参数类型的公共(public)构造函数,再实例化。
getConstructor 方法的功能是调用构造方法,可配合 newInstance 实现实例化。
- 参数是构造函数列表类型
- e.g.
class44.getConstructor(List.class)
- 因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数。
- getConstructor 和 getMethod 的区别是 getConstructor 只获取构造方法。
- e.g.
获取构造函数 #
Constructor m = clazz.getConstructor(); // 获取构造函数对象
Object instance = m.newInstance(); // 使用构造函数创建实例
- Constructor 类的 newInstance() 方法就是专门用来创建新实例的
关于早期版本 newInstance() 的说明!!! #
==早期 Java 中可以通过 class.newInstance() 直接调用无参构造方法进行实例化==,但是从 Java9 开始这个用法已经被弃用了,不管构造方法有无参数,都要通过 getConstructor 方法(调用构造方法)来实例化。
class.newInstance() // deprecated since Java9
// Java9 之后正确的实例化过程,可以是无参构造
Class.forName("全类名").getDeclaredConstructor(<构造函数参数>).newInstance(<函数参数>);
关于 getDeclaredConstructor #
getDeclaredConstructor
相比 getConstructor
的区别在于:
getDeclaredConstructor
可以获取类中指定参数类型的任何构造函数,包括 public、protected、private 或默认包访问。getConstructor
只能获取 public 的构造函数
在 Java9 之后,使用 getDeclaredConstructor
获取私有构造方法需要额外设置:
- 设置 setAccessible:
constructor.setAccessible(true);
- 执行时额外加上参数:
java --add-opens java.base/java.lang=ALL-UNNAMED Exec
以 Runtime 类为例说明这个问题,
import java.lang.reflect.Constructor;
public class Exec{
public static void main(String[] args) throws Exception{
// 获取类
Class class44 = Class.forName("java.lang.Runtime");
// 获取私有构造函数
Constructor constructor44 = class44.getDeclaredConstructor();
// 由于是私有,所以需要设置 setAccessible
constructor44.setAccessible(true);
// 由构造函数实例化,并执行实例的方法 exec 执行命令
class44.getMethod("exec", String.class).invoke(constructor44.newInstance(), "open -a Calculator.app");
}
}
为什么要用 invoke #
invoke 可以实现强制类型转换。
假如不用 invoke,需要额外写一个强制类型转换才能不报错:
((Runtime)constructor44.newInstance()).exec("open -a Calculator");
但是,实战中一般不会提供强制类型转换,所以需要通过 invoke
绕过类型检查,invoke
允许我们在运行时动态调用方法,而不需要在编译时知道确切的类型。
Runtime类 和 ProcessBuilder类 #
这是两个常见的用于命令执行的类:
- Runtime 类需要调用 exec 方法执行命令,命令参数传给 exec 方法
- ProcessBuilder 类需要调用 start 方法执行命令,命令参数直接在实例化的时候传给实例
另外,ProcessBuilder 的构造函数是 public 的,所以直接用 getConstructor
即可。
看一下 ProcessBuilder 执行命令和上一节 Runtime 的区别:
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
public class Exec{
public static void main(String[] args) throws Exception{
// 获取类
Class class44 = Class.forName("java.lang.ProcessBuilder");
// 获取一个参数类型为list的构造函数
Constructor constructor44 = class44.getConstructor(List.class);
// 命令参数直接在实例化的时候传给实例
class44.getMethod("start").invoke(constructor44.newInstance(Arrays.asList("open", "-a", "Calculator")));
}
}
当然,可以把三句写成两句,更简短一点:
Class class44 = Class.forName("java.lang.ProcessBuilder");
class44.getMethod("start").invoke(class44.getConstructor(List.class).newInstance(Arrays.asList("open", "-a", "Calculator")));
当然,也可以用 String[]
类型的构造函数:
public class Exec{
public static void main(String[] args) throws Exception{
Class class44 = Class.forName("java.lang.ProcessBuilder");
Object object44 = class44.getConstructor(String[].class).newInstance((Object) new String[]{"open", "-a", "Calculator.app"});
class44.getMethod("start").invoke(object44);
}
}
在 Java18 之后,Runtime类的 exec 方法被弃用,高版本推荐使用 ProcessBuilder。