Java反射机制详解(附带实例)
通过 Java 的反射机制,程序员可以更深入地控制程序的运行过程。
Java 反射机制可以在程序中访问已经被装载到 JVM 中的 Java 对象的描述,实现访问、检测和修改描述 Java 对象本身信息的功能。
Java 反射机制的功能十分强大,在 java.lang.reflect 包中提供了对该功能的支持。
众所周知,所有 Java 类均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,该方法返回一个类型为 Class 的对象。例如下面的代码:
在通过 getFields() 方法和 getMethods() 方法依次获得权限为 public 的成员变量和方法时,将包含从超类中继承的成员变量和方法;而通过 getDeclaredFields() 方法和 getDeclaredMethods() 方法只是获得在本类中定义的所有成员变量和方法。
每个 Constructor 对象代表一个构造方法,利用 Constructor 对象可以操纵相应的构造方法:
如果是访问指定的构造方法,则需要根据该构造方法的入口参数的类型来访问。例如,访问一个入口参数类型依次为String型和int型的构造方法,通过下面两种方式均可实现:
通过 java.lang.reflect.Modifier 类可以解析出 getModifiers() 方法的返回值所表示的修饰符信息,在该类中提供了一系列用来解析的静态方法,既能查看是否被指定的修饰符修饰,又能以字符串的形式获得所有修饰符。
Modifier 类常用静态方法如下表所示:
例如,判断对象 constructor 代表的构造方法是否被 private 修饰,以及以字符串形式获得该构造方法的所有修饰符的典型代码如下:
【实例】反射一个类的所有构造方法。在 com.mr 包下创建一个 Demo1 类,在该类中声明一个 String 型成员变量和 3 个 int 型成员变量,并提供 3 个构造方法。具体代码如下:
Field 类中提供的常用方法如下表所示。
【实例】反射一个类的所有成员变量。在 com.mr 包下创建一个 Demo2 类,在该类中依次声明一个 int、float、boolean 和 String 型的成员变量,并将它们设置为不同的访问权限。具体代码如下:
然后通过反射访问 com.mr.Demo2 类中的所有成员变量,将成员变量的名称和类型信息输出到控制台中,并分别将各个成员变量在修改前后的值输出到控制台中。关键代码如下:
如果是访问指定的方法,需要根据该方法的名称和入口参数的类型来访问。例如,访问一个名称为 print、入口参数类型依次为 String 型和 int 型的方法,通过下面两种方式均可实现:
Method 类中提供的常用方法如下表所示。
【实例】反射一个类的所有成员方法。在 com.mr 包下创建一个 Demo3 类,并编写 4 个成员方法。具体代码如下:
然后通过反射访问 com.mr.Demo3 类中的所有方法,将各个方法的名称、入口参数类型、返回值类型等信息输出到控制台中,并执行部分方法。关键代码如下:
Java 反射机制可以在程序中访问已经被装载到 JVM 中的 Java 对象的描述,实现访问、检测和修改描述 Java 对象本身信息的功能。
Java 反射机制的功能十分强大,在 java.lang.reflect 包中提供了对该功能的支持。
众所周知,所有 Java 类均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,该方法返回一个类型为 Class 的对象。例如下面的代码:
JTextField textField = new JTextField(); // 创建 JTextField 对象 Class textFieldC = textField.getClass(); // 获取 Class 对象利用 Class 类的对象 textFieldC,可以访问用来返回该对象的 textField 对象的描述信息。可以访问的主要描述信息如下表所示。
组成部分 | 访问方法 | 返回值类型 | 说明 |
---|---|---|---|
包路径 | getPackage() | Package 对象 | 获得该类的存储路径 |
类名称 | getName() | String 对象 | 获得该类的名称 |
继承类 | getSuperclass() | Class 对象 | 获得该类继承的类 |
实现接口 | getInterfaces() | Class 数组 | 获得该类实现的所有接口 |
构造方法 |
getConstructors() getConstructor(Class...parameterTypes) getDeclaredConstructors() getDeclaredConstructor(Class...parameterTypes) |
Constructor 型数组 Constructor 对象 Constructor 型数组 Constructor 对象 |
获得所有权限为 public 的构造方法 获得权限为 public 的指定构造方法 获得所有构造方法 获得指定的构造方法 |
方法 |
getMethods() getMethod(String name, Class...parameterTypes) getDeclaredMethods() getDeclaredMethod(String name, Class...parameterTypes) |
Method 型数组 Method 对象 Method 型数组 Method 对象 |
获得所有权限为 public 的方法 获得权限为 public 的指定方法 获得所有方法 获得指定的方法 |
成员变量 |
getFields() getField(String name) getDeclaredFields() getDeclaredField(String name) |
Field 型数组 Field 对象 Field 型数组 Field 对象 |
获得所有权限为 public 的成员变量 获得权限为 public 的指定的成员变量 获得所有成员变量 获得指定的成员变量 |
内部类 |
getClasses() getDeclaredClasses() |
Class 型数组 Class 型数组 |
获得所有权限为 public 的内部类 获得所有内部类 |
内部类的声明类 | getDeclaringClass() | Class 对象 | 如果该类为内部类,则返回它的成员类,否则返回 null |
在通过 getFields() 方法和 getMethods() 方法依次获得权限为 public 的成员变量和方法时,将包含从超类中继承的成员变量和方法;而通过 getDeclaredFields() 方法和 getDeclaredMethods() 方法只是获得在本类中定义的所有成员变量和方法。
访问构造方法
在通过下列一组方法访问构造方法时,将返回 Constructor 类型的对象或数组。每个 Constructor 对象代表一个构造方法,利用 Constructor 对象可以操纵相应的构造方法:
getConstructors() getConstructor(Class<?>...parameterTypes) getDeclaredConstructors() getDeclaredConstructor(Class<?>...parameterTypes)
如果是访问指定的构造方法,则需要根据该构造方法的入口参数的类型来访问。例如,访问一个入口参数类型依次为String型和int型的构造方法,通过下面两种方式均可实现:
objectClass.getDeclaredConstructor(String.class, int.class); objectClass.getDeclaredConstructor(new Class[] { String.class, int.class });Constructor 类中提供的常用方法如下表所示:
方法 | 说明 |
---|---|
isVarArgs() | 查看该构造方法是否允许带有可变数量的参数,如果允许,则返回 true,否则返回 false |
getParameterTypes() | 按照声明顺序以 Class 数组的形式获得该构造方法的各个参数的类型 |
getExceptionTypes() | 以 Class 数组的形式获得该构造方法可能抛出的异常类型 |
newInstance(Object... initargs) | 该构造方法利用指定参数创建一个该类的对象,如果该构造方法中未设置参数,则表示采用默认无参数的构造方法 |
setAccessible(boolean flag) | 如果该构造方法的权限为 private,则该构造方法默认认为不允许通过反射利用 newInstance(Object... initargs)方法创建对象,因此先执行该方法,并将入口参数设为 true,则允许创建对象 |
getModifiers() | 获得一个整数,该整数可以解析出该构造方法采用的修饰符 |
通过 java.lang.reflect.Modifier 类可以解析出 getModifiers() 方法的返回值所表示的修饰符信息,在该类中提供了一系列用来解析的静态方法,既能查看是否被指定的修饰符修饰,又能以字符串的形式获得所有修饰符。
Modifier 类常用静态方法如下表所示:
静态方法 | 说明 |
---|---|
isPublic(int mod) | 查看是否被 public 修饰符修饰,如果是则返回 true,否则返回 false |
isProtected(int mod) | 查看是否被 protected 修饰符修饰,如果是则返回 true,否则返回 false |
isPrivate(int mod) | 查看是否被 private 修饰符修饰,如果是则返回 true,否则返回 false |
isStatic(int mod) | 查看是否被 static 修饰符修饰,如果是则返回 true,否则返回 false |
isFinal(int mod) | 查看是否被 final 修饰符修饰,如果是则返回 true,否则返回 false |
toString(int mod) | 以字符串的形式返回所有修饰符 |
例如,判断对象 constructor 代表的构造方法是否被 private 修饰,以及以字符串形式获得该构造方法的所有修饰符的典型代码如下:
int modifiers = constructor.getModifiers(); boolean isEmbellishByPrivate = Modifier.isPrivate(modifiers); String embellishment = Modifier.toString(modifiers);
【实例】反射一个类的所有构造方法。在 com.mr 包下创建一个 Demo1 类,在该类中声明一个 String 型成员变量和 3 个 int 型成员变量,并提供 3 个构造方法。具体代码如下:
package com.mr; public class Demo1 { String s; int i, i2, i3; private Demo1() { } protected Demo1(String s, int i) { this.s = s; this.i = i; } public Demo1(String... strings) throws NumberFormatException { if (0 < strings.length) i = Integer.valueOf(strings[0]); if (1 < strings.length) i2 = Integer.valueOf(strings[1]); if (2 < strings.length) i3 = Integer.valueOf(strings[2]); } public void print() { System.out.println("s=" + s); System.out.println("i=" + i); System.out.println("i2=" + i2); System.out.println("i3=" + i3); } }然后编写测试类 ConstructorDemo1,在该类中通过反射访问 com.mr.Demo1 类中的所有构造方法,并将该构造方法是否允许带有可变数量的参数、入口参数类型和可能抛出的异常类型信息输出到控制台中。具体代码如下:
import java.lang.reflect.Constructor; import com.mr.Demo1; public class ConstructorDemo1 { public static void main(String[] args) { Demo1 d1 = new Demo1("10", "20", "30"); Class<?> extendsDemo1 = d1.getClass(); // 获得所有构造方法 Constructor<?>[] declaredConstructors = Demo1.class.getDeclaredConstructors(); for (int i = 0; i < declaredConstructors.length; i++) { Constructor<?> constructor = declaredConstructors[i]; System.out.println("查看是否允许带有可变数量的参数:" + constructor.isVarArgs()); System.out.println("该构造方法的入口参数类型依次为:"); Class<?>[] parameterTypes = constructor.getParameterTypes(); // 获取所有参数类型 for (int j = 0; j < parameterTypes.length; j++) { System.out.println(" " + parameterTypes[j]); } System.out.println("该构造方法可能抛出的异常类型为:"); // 获得所有可能抛出的异常信息类型 Class<?>[] exceptionTypes = constructor.getExceptionTypes(); for (int j = 0; j < exceptionTypes.length; j++) { System.out.println(" " + exceptionTypes[j]); } Demo1 d2 = null; while (d2 == null) { try { // 如果构造方法的访问权限为 private 或 protected,则抛出异常,即不允许访问 if (i == 2) // 通过执行默认没有参数的构造方法创建对象 d2 = (Demo1) constructor.newInstance(); else if (i == 1) // 通过执行具有两个参数的构造方法创建对象 d2 = (Demo1) constructor.newInstance("7", 5); else { // 通过执行具有可变数量参数的构造方法创建对象 Object[] parameters = new Object[]{new String[]{"100", "200", "300"}}; d2 = (Demo1) constructor.newInstance(parameters); } } catch (Exception e) { System.out.println("在创建对象时抛出异常,下面执行 setAccessible()方法"); constructor.setAccessible(true); // 设置为允许访问 } } if (d2 != null) { d2.print(); System.out.println(); } } } }运行结果如下:
查看是否允许带有可变数量的参数:true 该构造方法的入口参数类型依次为: class [Ljava.lang.String; 该构造方法可能抛出的异常类型为: class java.lang.NumberFormatException s=null i=100 i2=200 i3=300 查看是否允许带有可变数量的参数:false 该构造方法的入口参数类型依次为: class java.lang.String int 该构造方法可能抛出的异常类型为: 在创建对象时抛出异常,下面执行 setAccessible()方法 s=7 i=5 i2=0 i3=0 查看是否允许带有可变数量的参数:false 该构造方法的入口参数类型依次为: 该构造方法可能抛出的异常类型为: 在创建对象时抛出异常,下面执行 setAccessible()方法 s=null i=0 i2=0 i3=0当反射无参构造方法时将输出所有属性的默认值,当反射有参数的构造方法时将输出所有属性被赋予的相应值。
访问成员变量
在通过下列一组方法访问成员变量时,将返回 Field 类型的对象或数组。每个 Field 对象代表一个成员变量,利用 Field 对象可以操纵相应的成员变量:getFields() getField(String name) getDeclaredFields() getDeclaredField(String name)如果是访问指定的成员变量,则可以通过该成员变量的名称来访问。例如,访问一个名称为 birthday 的成员变量,访问方法如下:
object. getDeclaredField("birthday");
Field 类中提供的常用方法如下表所示。
方法 | 说明 |
---|---|
getName() | 获得该成员变量的名称 |
getType() | 获得表示该成员变量类型的 Class 对象 |
get(Object obj) | 获得指定对象 obj 中成员变量的值,返回值为 Object 型 |
set(Object obj, Object value) | 将指定对象 obj 中成员变量的值设置为 value |
getInt(Object obj) | 获得指定对象 obj 中类型为 int 的成员变量的值 |
setInt(Object obj, int i) | 将指定对象 obj 中类型为 int 的成员变量的值设置为 i |
getFloat(Object obj) | 获得指定对象 obj 中类型为 float 的成员变量的值 |
setFloat(Object obj, float f) | 将指定对象 obj 中类型为 float 的成员变量的值设置为 f |
getBoolean(Object obj) | 获得指定对象 obj 中类型为 boolean 的成员变量的值 |
setBoolean(Object obj, boolean z) | 将指定对象 obj 中类型为 boolean 的成员变量的值设置为 z |
setAccessible(boolean flag) | 此方法可以设置是否忽略权限限制直接访问 private 等私有权限的成员变量 |
getModifiers() | 获得可以解析出该成员变量所采用修饰符的整数 |
【实例】反射一个类的所有成员变量。在 com.mr 包下创建一个 Demo2 类,在该类中依次声明一个 int、float、boolean 和 String 型的成员变量,并将它们设置为不同的访问权限。具体代码如下:
package com.mr; public class Demo2 { int i; public float f; protected boolean b; private String s; }
然后通过反射访问 com.mr.Demo2 类中的所有成员变量,将成员变量的名称和类型信息输出到控制台中,并分别将各个成员变量在修改前后的值输出到控制台中。关键代码如下:
import java.lang.reflect.Field; import com.mr.Demo2; public class FieldDemo { public static void main(String[] args) { Demo2 example = new Demo2(); Class<?> exampleC = example.getClass(); // 获取所有成员变量 Field[] declaredFields = exampleC.getDeclaredFields(); // 遍历成员变量 for (int i = 0; i < declaredFields.length; i++) { Field field = declaredFields[i]; System.out.println("名称为:" + field.getName()); // 获取成员变量名称并输出 Class<?> fieldType = field.getType(); // 获取成员变量类型 System.out.println("类型为:" + fieldType); boolean isTurn = true; while (isTurn) { // 如果该成员变量的访问权限为 private 或 protected,则抛出异常,即不允许访问 try { // 获得成员变量值 System.out.println("修改前的值为:" + field.get(example)); if (fieldType.equals(int.class)) { // 判断成员变量的类型是否为 int 型 System.out.println("利用方法 setInt()修改成员变量的值"); field.setInt(example, 168); // 为 int 型成员变量赋值 } else if (fieldType.equals(float.class)) { // 判断成员变量的类型是否为 float 型 System.out.println("利用方法 setFloat()修改成员变量的值"); field.setFloat(example, 99.9F); // 为 float 型成员变量赋值 } else if (fieldType.equals(boolean.class)) { // 判断成员变量的类型是否为 boolean 型 System.out.println("利用方法 setBoolean()修改成员变量的值"); field.setBoolean(example, true); // 为 boolean 型成员变量赋值 } else { System.out.println("利用方法 set()修改成员变量的值"); field.set(example, "MWQ"); // 可以为各种类型的成员变量赋值 } // 获得成员变量值 System.out.println("修改后的值为:" + field.get(example)); } catch (Exception e) { System.out.println("在设置成员变量值时抛出异常," + "下面执行 setAccessible()方法!"); field.setAccessible(true); // 设置为允许访问 isTurn = true; } } System.out.println(); } } }运行结果如下:
名称为:i 类型为:int 在设置成员变量值时抛出异常,下面执行 setAccessible()方法! 修改前的值为:0 利用方法 setInt()修改成员变量的值 修改后的值为:168 名称为:f 类型为:float 修改前的值为:0.0 利用方法 setFloat()修改成员变量的值 修改后的值为:99.9 名称为:b 类型为:boolean 在设置成员变量值时抛出异常,下面执行 setAccessible()方法! 修改前的值为:false 利用方法 setBoolean()修改成员变量的值 修改后的值为:true 名称为:s 类型为:class java.lang.String 在设置成员变量值时抛出异常,下面执行 setAccessible()方法! 修改前的值为:null 利用方法 set()修改成员变量的值 修改后的值为:MWQ通过这个结果可以看出,在反射权限为 private 或 protected 的成员变量时,需要执行 setAccessible() 方法,并将入口参数设为 true,否则不允许访问。
访问成员方法
在通过下列一组方法访问成员方法时,将返回 Method 类型的对象或数组。每个 Method 对象代表一个方法,利用 Method 对象可以操纵相应的方法:getMethods() getMethod(String name, Class<?>...parameterTypes) getDeclaredMethods() getDeclaredMethod(String name, Class<?>...parameterTypes)
如果是访问指定的方法,需要根据该方法的名称和入口参数的类型来访问。例如,访问一个名称为 print、入口参数类型依次为 String 型和 int 型的方法,通过下面两种方式均可实现:
objectClass.getDeclaredMethod("print", String.class, int.class); objectClass.getDeclaredMethod("print", new Class[]{String.class, int.class });
Method 类中提供的常用方法如下表所示。
方法 | 说明 |
---|---|
getName() | 获得该方法的名称 |
getParameterTypes() | 按照声明顺序以 Class 数组的形式获得该方法的各个参数的类型 |
getReturnType() | 以 Class 对象的形式获得该方法的返回值的类型 |
getExceptionTypes() | 以 Class 数组的形式获得该方法可能抛出的异常类型 |
invoke(Object obj, Object...args) | 利用指定参数 args 执行指定对象 obj 中的该方法,返回值为 Object 型 |
isVarArgs() | 查看该方法是否允许带有可变数量的参数,如果允许则返回 true,否则返回 false |
getModifiers() | 获得一个整数,该整数可以解析出该方法采用的修饰符 |
【实例】反射一个类的所有成员方法。在 com.mr 包下创建一个 Demo3 类,并编写 4 个成员方法。具体代码如下:
package com.mr; public class Demo3 { static void staticMethod() { System.out.println("执行 staticMethod()方法"); } public int publicMethod(int i) { System.out.println("执行 publicMethod()方法"); return i * 100; } protected int protectedMethod(String s, int i) throws NumberFormatException { System.out.println("执行 protectedMethod()方法"); return Integer.valueOf(s) + i; } private String privateMethod(String... strings) { System.out.println("执行 privateMethod()方法"); StringBuffer stringBuffer = new StringBuffer(); for (int i = 0; i < strings.length; i++) { stringBuffer.append(strings[i]); } return stringBuffer.toString(); } }
然后通过反射访问 com.mr.Demo3 类中的所有方法,将各个方法的名称、入口参数类型、返回值类型等信息输出到控制台中,并执行部分方法。关键代码如下:
import java.lang.reflect.*; import com.mr.Demo3; public class MethodDemo { public static void main(String[] args) { Demo3 demo = new Demo3(); Class<?> demoClass = demo.getClass(); Method[] declaredMethods = demoClass.getDeclaredMethods(); // 获得所有方法 for (int i = 0; i < declaredMethods.length; i++) { Method method = declaredMethods[i]; System.out.println("名称为:" + method.getName()); // 获得方法名称 System.out.println("是否允许带有可变数量的参数:" + method.isVarArgs()); System.out.println("入口参数类型依次为:"); Class<?>[] parameterTypes = method.getParameterTypes(); // 获得所有参数类型 for (int j = 0; j < parameterTypes.length; j++) { System.out.println(" " + parameterTypes[j]); } System.out.println("返回值类型为:" + method.getReturnType()); // 获得方法返回值类型 System.out.println("可能抛出的所有异常类型有:"); // 获得所有可能抛出的所有异常类型 Class<?>[] exceptionTypes = method.getExceptionTypes(); for (int j = 0; j < exceptionTypes.length; j++) { System.out.println(" " + exceptionTypes[j]); } boolean isTurn = true; while (isTurn) { try { // 如果该方法的访问权限为 private 或 protected,则抛出异常,即不允许访问 isTurn = false; if ("staticMethod".equals(method.getName())) // 执行没有入口参数的方法 method.invoke(demo); else if ("publicMethod".equals(method.getName())) System.out.println("返回值为:" + method.invoke(demo, 168)); // 执行方法 else if ("protectedMethod".equals(method.getName())) System.out.println("返回值为:" + method.invoke(demo, "7", 5)); // 执行方法 else if ("privateMethod".equals(method.getName())) { // 定义二维数组 Object[] parameters = new Object[]{new String[]{"M", "W", "Q"}}; System.out.println("返回值为:" + method.invoke(demo, parameters)); } } catch (Exception e) { System.out.println("在执行方法时抛出异常," + "下面执行 setAccessible()方法!"); method.setAccessible(true); // 设置为允许访问 isTurn = true; } } System.out.println(); } } }运行结果如下,程序将依次访问 staticMethod()、publicMethod()、protectedMethod() 和 privateMethod() 方法:
名称为:staticMethod 是否允许带有可变数量的参数:false 入口参数类型依次为: 返回值类型为:void 可能抛出的所有异常类型有: 执行 staticMethod()方法 名称为:publicMethod 是否允许带有可变数量的参数:false 入口参数类型依次为: int 返回值类型为:int 可能抛出的所有异常类型有: 执行 publicMethod()方法 返回值为:16800 名称为:protectedMethod 是否允许带有可变数量的参数:false 入口参数类型依次为: class java.lang.String int 返回值类型为:int 可能抛出的所有异常类型有: class java.lang.NumberFormatException 在执行方法时抛出异常,下面执行 setAccessible()方法! 执行 protectedMethod()方法 返回值为:12 名称为:privateMethod 是否允许带有可变数量的参数:true 入口参数类型依次为: class [Ljava.lang.String; 返回值类型为:class java.lang.String 可能抛出的所有异常类型有: 在执行方法时抛出异常,下面执行 setAccessible()方法! 执行 privateMethod()方法 返回值为:MWQ
注意,在反射中执行具有可变数量的参数的构造方法时,需要将入口参数定义成二维数组。