Java反射机制是 Java 语言的一个重要特性。
在学习 Java 反射机制前,大家应该先了解两个概念,编译期和运行期。
编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
反射机制是什么?
Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。
对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。
为什么要用反射?
Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect
包比如:
Java 反射机制主要包括以下几个方面:
- 获取类的信息:反射机制允许程序在运行时获取一个类的信息,例如:类的名称、修饰符、父类、接口、属性和方法等。
- 创建对象:通过反射机制,程序可以在运行时创建一个类的实例对象,而不需要在编译时知道这个类的类型。
- 访问属性:通过反射机制,程序可以在运行时访问一个对象的属性,例如:获取属性值、设置属性值、获取属性的类型和修饰符等。
- 调用方法:通过反射机制,程序可以在运行时调用一个对象的方法,例如:获取方法的参数、调用方法、获取方法的返回值等。
- 修改访问权限:通过反射机制,程序可以在运行时修改对象的访问权限,例如:设置属性或方法的访问权限为 public、private 或 protected。
Java 反射机制在实际应用中有广泛的用途,例如:框架、动态代理、注解处理器等。反射机制虽然功能强大,但是也有一些缺点,例如:性能较低、安全性较差等,因此在使用时需要注意。
要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件(.class)对应的 Class 类型的对象。
众所周知,所有 Java 类均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,该方法返回同一个类型为 Class 的对象。
表列出了通过反射可以访问的信息。
类型 | 访问方法 | 返回值类型 | 说明 |
---|---|---|---|
包路径 | getPackage() | Package 对象 | 获取该类的存放路径 |
类名称 | getName() | String 对象 | 获取该类的名称 |
继承类 | getSuperclass() | Class 对象 | 获取该类继承的类 |
实现接口 | getlnterfaces() | Class 型数组 | 获取该类实现的所有接口 |
构造方法 | getConstructors() | Constructor 型数组 | 获取所有权限为 public 的构造方法 |
getDeclaredContruectors() | Constructor 对象 | 获取当前对象的所有构造方法 | |
方法 | getMethods() | Methods 型数组 | 获取所有权限为 public 的方法 |
getDeclaredMethods() | Methods 对象 | 获取当前对象的所有方法 | |
成员变量 | getFields() | Field 型数组 | 获取所有权限为 public 的成员变量 |
getDeclareFileds() | Field 对象 | 获取当前对象的所有成员变量 | |
内部类 | getClasses() | Class 型数组 | 获取所有权限为 public 的内部类 |
getDeclaredClasses() | Class 型数组 | 获取所有内部类 | |
内部类的声明类 | getDeclaringClass() | Class 对象 | 如果该类为内部类,则返回它的成员类,否则返回 null |
反射机制的优缺点
优点:
- 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
- 与 Java 动态编译相结合,可以实现无比强大的功能。
- 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点:
- 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
- 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。
反射机制API
java.lang.Class 类
java.lang.Class 类是实现反射的关键所在,Class 类的一个实例表示 Java 的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和 void。Class 没有公有的构造方法,Class 实例是由 JVM 在类加载时自动创建的。
在程序代码中获得 Class 实例可以通过如下代码实现:
// 1. 通过类型class静态变量
Class clz1 = String.class;
String str = "Hello";
// 2. 通过对象的getClass()方法
Class clz2 = str.getClass();
每一种类型包括类和接口等,都有一个 class 静态变量可以获得 Class 实例。另外,每一个对象都有 getClass() 方法可以获得 Class 实例,该方法是由 Object 类提供的实例方法。
Class 类提供了很多方法可以获得运行时对象的相关信息,下面的程序代码展示了其中一些方法。
public class ReflectionTest01 {public static void main(String[] args) {// 获得Class实例// 1.通过类型class静态变量Class clz1 = String.class;String str = "Hello";// 2.通过对象的getClass()方法Class clz2 = str.getClass();// 获得int类型Class实例Class clz3 = int.class;// 获得Integer类型Class实例Class clz4 = Integer.class;System.out.println("clz2类名称:" + clz2.getName());System.out.println("clz2是否为接口:" + clz2.isInterface());System.out.println("clz2是否为数组对象:" + clz2.isArray());System.out.println("clz2父类名称:" + clz2.getSuperclass().getName());System.out.println("clz2是否为基本类型:" + clz2.isPrimitive());System.out.println("clz3是否为基本类型:" + clz3.isPrimitive());System.out.println("clz4是否为基本类型:" + clz4.isPrimitive());}
}
运行结果如下:
clz2类名称:java.lang.String
clz2是否为接口:false
clz2是否为数组对象:false
clz2父类名称:java.lang.Object
clz2是否为基本类型:false
clz3是否为基本类型:true
clz4是否为基本类型:false
java.lang.reflect 包
java.lang.reflect 包提供了反射中用到类,主要的类说明如下:
- Constructor 类:提供类的构造方法信息。
- Field 类:提供类或接口中成员变量信息。
- Method 类:提供类或接口成员方法信息。
- Array 类:提供了动态创建和访问 Java 数组的方法。
- Modifier 类:提供类和成员访问修饰符信息。
示例代码如下:
public class ReflectionTest02 {public static void main(String[] args) {try {// 动态加载xx类的运行时对象Class c = Class.forName("java.lang.String");// 获取成员方法集合Method[] methods = c.getDeclaredMethods();// 遍历成员方法集合for (Method method : methods) {// 打印权限修饰符,如public、protected、privateSystem.out.print(Modifier.toString(method.getModifiers()));// 打印返回值类型名称System.out.print(" " + method.getReturnType().getName() + " ");// 打印方法名称System.out.println(method.getName() + "();");}} catch (ClassNotFoundException e) {System.out.println("找不到指定类");}}
}
通过反射访问构造方法
为了能够动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个 Constructor
类型的对象或者数组。
- getConstructors()
- getConstructor(Class<?>…parameterTypes)
- getDeclaredConstructors()
- getDeclaredConstructor(Class<?>...parameterTypes)
如果是访问指定的构造方法,需要根据该构造方法的入口参数的类型来访问。例如,访问一个入口参数类型依次为 int 和 String 类型的构造方法,下面的两种方式均可以实现。
1、objectClass.getDeclaredConstructor(int.class,String.class);
2、objectClass.getDeclaredConstructor(new Class[]{int.class,String.class});
创建的每个 Constructor 对象表示一个构造方法,然后利用 Constructor 对象的方法操作构造方法。Constructor 类的常用方法如表所示。
方法名称 | 说明 |
---|---|
isVarArgs() | 查看该构造方法是否允许带可变数量的参数,如果允许,返回 true,否则返回 false |
getParameterTypes() | 按照声明顺序以 Class 数组的形式获取该构造方法各个参数的类型 |
getExceptionTypes() | 以 Class 数组的形式获取该构造方法可能抛出的异常类型 |
newInstance(Object … initargs) | 通过该构造方法利用指定参数创建一个该类型的对象,如果未设置参数则表示 采用默认无参的构造方法 |
setAccessiable(boolean flag) | 如果该构造方法的权限为 private,默认为不允许通过反射利用 netlnstance() 方法创建对象。如果先执行该方法,并将入口参数设置为 true,则允许创建对 象 |
getModifiers() | 获得可以解析出该构造方法所采用修饰符的整数 |
通过 java.lang.reflect.Modifier 类可以解析出 getMocMers() 方法的返回值所表示的修饰符信息。在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以字符串的形式获得所有修饰符。表列出了 Modifier 类的常用静态方法。
静态方法名称 | 说明 | |
---|---|---|
isStatic(int mod) | 如果使用 static 修饰符修饰则返回 true,否则返回 false | |
isPublic(int mod) | 如果使用 public 修饰符修饰则返回 true,否则返回 false | |
isProtected(int mod) | 如果使用 protected 修饰符修饰则返回 true,否则返回 false | |
isPrivate(int mod) | 如果使用 private 修饰符修饰则返回 true,否则返回 false | |
isFinal(int mod) | 如果使用 final 修饰符修饰则返回 true,否则返回 false | |
toString(int mod) | 以字符串形式返回所有修饰符 |
例如,下列代码判断对象 con 所代表的构造方法是否被 public 修饰,以及以字符串形式获取该构造方法的所有修饰符。
int modifiers = con.getModifiers(); // 获取构造方法的修饰符整数
boolean isPublic = Modifier.isPublic(modifiers); // 判断修饰符整数是否为public
string allModifiers = Modifier.toString(modifiers);
通过反射执行方法(获取方法)
要动态获取一个对象方法的信息,首先需要通过下列方法之一创建一个 Method
类型的对象或者数组。
- getMethods()
- getMethods(String name,Class<?> …parameterTypes)
- getDeclaredMethods()
- getDeclaredMethods(String name,Class<?>...parameterTypes)
如果是访问指定的构造方法,需要根据该方法的入口参数的类型来访问。例如,访问一个名称为 max,入口参数类型依次为 int 和 String 类型的方法。
下面的两种方式均可以实现:
1、objectClass.getDeclaredConstructor("max",int.class,String.class);
2、objectClass.getDeclaredConstructor("max",new Class[]{int.class,String.class});
Method 类的常用方法如表 3 所示。
静态方法名称 | 说明 |
---|---|
getName() | 获取该方法的名称 |
getParameterType() | 按照声明顺序以 Class 数组的形式返回该方法各个参数的类型 |
getReturnType() | 以 Class 对象的形式获得该方法的返回值类型 |
getExceptionTypes() | 以 Class 数组的形式获得该方法可能抛出的异常类型 |
invoke(Object obj,Object...args) | 利用 args 参数执行指定对象 obj 中的该方法,返回值为 Object 类型 |
isVarArgs() | 查看该方法是否允许带有可变数量的参数,如果允许返回 true,否则返回 false |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |
通过反射访问成员变量
通过下列任意一个方法访问成员变量时将返回 Field 类型的对象或数组。
- getFields()
- getField(String name)
- getDeclaredFields()
- getDeclaredField(String name)
上述方法返回的 Field 对象代表一个成员变量。例如,要访问一个名称为 price 的成员变量,示例代码如下:
object.getDeciaredField("price");
Field 类的常用方法如表所示
方法名称 | 说明 |
---|---|
getName() | 获得该成员变量的名称 |
getType() | 获取表示该成员变量的 Class 对象 |
get(Object obj) | 获得指定对象 obj 中成员变量的值,返回值为 Object 类型 |
set(Object obj, Object value) | 将指定对象 obj 中成员变量的值设置为 value |
getlnt(0bject obj) | 获得指定对象 obj 中成员类型为 int 的成员变量的值 |
setlnt(0bject obj, int i) | 将指定对象 obj 中成员变量的值设置为 i |
setFloat(Object obj, float f) | 将指定对象 obj 中成员变量的值设置为 f |
getBoolean(Object obj) | 获得指定对象 obj 中成员类型为 boolean 的成员变量的值 |
setBoolean(Object obj, boolean b) | 将指定对象 obj 中成员变量的值设置为 b |
getFloat(Object obj) | 获得指定对象 obj 中成员类型为 float 的成员变量的值 |
setAccessible(boolean flag) | 此方法可以设置是否忽略权限直接访问 private 等私有权限的成员变量 |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |