首页 > 编程笔记 > Java笔记 阅读:56

Java反射机制详解(附带实例)

通过 Java 的反射机制,程序员可以更深入地控制程序的运行过程。

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 类中提供的常用方法如下表所示:

表: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 类常用静态方法如下表所示:

表: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 类中提供的常用方法如下表所示。

表: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 类中提供的常用方法如下表所示。

表: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

注意,在反射中执行具有可变数量的参数的构造方法时,需要将入口参数定义成二维数组。

相关文章