Java 反射技术总结


所谓反射就是在程序运行期间,能够动态获取到类的属性和方法,以及动态操作对象的属性和方法。

反射技术其实应用很广泛,尤其是各种框架技术都离不开反射,一些常用的 jar 包中间件(比如各个数据库厂商提供的 JDBC 访问驱动程序)也使用反射技术。之所以要总结一下反射技术,主要还是为了能够看懂相关 Java 框架相关源码,领悟其设计思想的巧妙之处,更好的学习和掌握各种框架。


首先我们先准备一个 Person 类,下面所有的代码演示,都使用该类,代码如下:

package com.jobs.reflect;

public class Person {
    //公开字段
    public String name;
    public int age;
    //私有字段
    private int salary;

    //------------------------------

    //无参构造函数
    public Person() { }

    //全参构造函数
    public Person(String name, int age, int salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    //私有构造函数
    private Person(String name, int age) {
        this.name = name;
        this.age = age;
        this.salary = 100;
        System.out.println("私有构造函数被调用...");
    }

    //------------------------------

    //私有方法
    private void method1() {
        System.out.println("私有方法 method1 被调用,无参无返回值");
    }

    public String method2(String name) {
        System.out.println("method2 被调用,有参有返回值,参数为" + name);
        return "method2 返回值为:" + name + " fastoder...";
    }
}

一、获取一个类的 Class 对象的三种方式

为什么要获取一个类的 Class 对象?

一个类的 Class 对象,可以理解为模板。
获取到一个类的 Class 对象后,就相当于获取到了一个类的模板,然后就可以进行以下操作:

  • 获取 Class 对象(模板)中的任意属性和方法。即使是私有属性和方法,也能够获取到。
  • 通过 Class 对象(模板)创建对象实例,参照模板中的属性和方法,可以为实例对象的属性赋值,以及调用其方法

获取一个类的 Class 模板对象,有三种方式:

  • 从文件中获取,调用 Class.forName(全类名) 方法
  • 在内存中获取,可以调用一个类的 Class 属性
  • 对于一个实例化的对象,可以调用其 getClass() 方法

无论采用哪种方式获取,获取到的对象,都是同一个对象。代码演示如下:

public class reflectDemo1 {

    public static void main(String[] args) throws ClassNotFoundException {

        //第 1 种方式获取 Person 的 Class 类对象
        Class cla1 = Class.forName("com.jobs.reflect.Person");
        System.out.println(cla1);

        //第 2 种方式获取 Person 的 Class 类对象
        Class cla2 = Person.class;
        System.out.println(cla2);

        //第 3 种方式获取 Person 的 Class 类对象
        Person per = new Person();
        Class cla3 = per.getClass();
        System.out.println(cla3);

        //无论哪种方式获取,获取到的都是 Person 的同一个类对象
        System.out.println(cla1 == cla2);  //true
        System.out.println(cla1 == cla3);  //true
    }
}

二、获取构造函数并创建实例

通过 getConstructors 方法,可以获取到所有的 public 修饰的构造方法

通过 getDeclaredConstructors 方法,可以获取到所有构造方法,包括 private 修饰的构造方法

通过 getConstructor(参数类型...) 方法,可以获取到 public 修饰的具体的一个构造方法

通过 getDeclaredConstructor(参数类型...) 方法,可以获取到具体的一个构造方法,包括私有构造方法

通过构造方法对象的 newInstance(参数值...) 方法,可以创建对象实例。

需要注意:如果想使用私有的构造方法对象创建实例对象,需要调用私有构造方法对象的 setAccessible(boolean flag) 方法,并传入 true 即可。(true 表示取消安全访问限制)

具体演示代码如下:

public class reflectDemo2 {
    public static void main(String[] args) {

        try {
            //获取 Person 的 Class 类对象
            Class cla = Class.forName("com.jobs.reflect.Person");

            System.out.println("Person类的所有构造方法为:");
            //获取所有的构造方法,包括私有构造方法
            Constructor[] constructors = cla.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }

            System.out.println("--------------------------");

            //获取无参构造方法,创建实例对象
            Constructor constructor1 = cla.getConstructor();
            Person per1 = (Person) constructor1.newInstance();
            System.out.println(per1); //打印实例化对象的地址值

            //获取全参构造方法,创建实例对象
            Constructor constructor2 = cla.getConstructor(String.class, int.class, int.class);
            Person per2 = (Person) constructor2.newInstance("侯胖胖", 40, 22000);
            System.out.println(per2); //打印实例化对象的地址值

            //获取私有构造方法,创建实例对象
            Constructor constructor3 = cla.getDeclaredConstructor(String.class, int.class);
            //取消安全访问限制
            constructor3.setAccessible(true);
            Person per3 = (Person) constructor3.newInstance("任肥肥", 38);
            System.out.println(per3); //打印实例化对象的地址值

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

/* 
打印出的结果如下:
Person类的所有构造方法为:
private com.jobs.reflect.Person(java.lang.String,int)
public com.jobs.reflect.Person(java.lang.String,int,int)
public com.jobs.reflect.Person()
--------------------------
com.jobs.reflect.Person@12edcd21
com.jobs.reflect.Person@34c45dca
私有构造函数被调用...
com.jobs.reflect.Person@52cc8049
*/

三、获取字段并赋值

通过 getFields() 方法,可以获取到所有的 public 修饰的字段

通过 getDeclaredFields() 方法,可以获取到所有字段,包括私有字段

通过 getField(String name) 方法,可以获取到 public 修饰的具体一个字段

通过 getDeclaredField(String name) 方法,可以获取到具体一个字段,包括私有字段

通过 set(Object obj, Object value) 方法,给字段赋值,第一个参数是具体的实例化对象

通过 get(Object obj) 方法,获取具体一个字段的值,该参数是具体的实例化对象

需要注意:如果想要获取私有字段的值,以及设置私有字段的值,需要调用私有字段对象的 setAccessible(boolean flag) 方法,并传入 true 即可。(true 表示取消安全访问限制)

具体演示代码如下:

public class reflectDemo3 {
    public static void main(String[] args) {

        try {
            //获取 Person 的 Class 类对象
            Class cla = Class.forName("com.jobs.reflect.Person");

            System.out.println("Person类的所有字段为:");
            Field[] fields = cla.getDeclaredFields();
            for (Field field : fields) {
                System.out.println(field);
            }

            System.out.println("--------------------------");

            //获取无参构造方法,创建实例对象
            Constructor cst = cla.getConstructor();
            Person per = (Person) cst.newInstance();

            //给 name 赋值
            Field fieldName = cla.getField("name");
            fieldName.set(per, "乔豆豆");

            //给 age 赋值
            Field fieldAge = cla.getField("age");
            fieldAge.set(per, 38);

            //给私有字段 salary 复制
            Field fieldSalary = cla.getDeclaredField("salary");
            //取消安全访问限制
            fieldSalary.setAccessible(true);
            fieldSalary.set(per, 28000);

            //获取相应字段的值
            System.out.println("Person实例化对象的字段值为:");
            System.out.println(fieldName.getName() + " ---> " + fieldName.get(per));
            System.out.println(fieldAge.getName() + " ---> " + fieldAge.get(per));
            System.out.println(fieldSalary.getName() + " ---> " + fieldSalary.get(per));

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

/*
打印出的结果如下:
Person类的所有字段为:
public java.lang.String com.jobs.reflect.Person.name
public int com.jobs.reflect.Person.age
private int com.jobs.reflect.Person.salary
--------------------------
Person实例化对象的字段值为:
name ---> 乔豆豆
age ---> 38
salary ---> 28000
*/

四、获取方法并调用方法

通过 getMethods() 方法,可以获取到所有 public 修饰的方法,包括继承的方法

通过 getDeclaredMethods() 方法,可以获取到所有方法,但是不包括继承的方法

通过 getMethod(方法名, 参数类型...) 方法,可以获取到具体一个 public 修饰的方法

通过 getDeclaredMethod(方法名, 参数类型...) 方法,可以获取到具体一个方法,包括私有方法

通过 invoke(Object obj, 参数值...) 方法,可以调用具体一个方法,第一个参数是具体的实例化对象

需要注意:如果想要调用私有方法,需要调用私有方法对象的 setAccessible(boolean flag) 方法,并传入 true 即可。(true 表示取消安全访问限制)

具体演示代码如下:

public class reflectDemo4 {
    public static void main(String[] args) {

        try {
            //获取 Person 的 Class 类对象
            Class cla = Class.forName("com.jobs.reflect.Person");

            System.out.println("Person类的所有方法为:");
            Method[] methods = cla.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println(method);
            }

            System.out.println("--------------------------");

            //获取无参构造方法,创建实例对象
            Constructor cst = cla.getConstructor();
            Person per = (Person) cst.newInstance();

            //调用私有方法,私有方法无参数
            Method method1 = cla.getDeclaredMethod("method1");
            //取消私有方法的安全访问限制
            method1.setAccessible(true);
            method1.invoke(per);

            //调用有参数,有返回值的共有方法
            Method method2 = cla.getMethod("method2", String.class);
            String result = (String) method2.invoke(per, "任天蓬");
            System.out.println(result);

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

/*
打印出的结果如下:
Person类的所有方法为:
public java.lang.String com.jobs.reflect.Person.method2(java.lang.String)
private void com.jobs.reflect.Person.method1()
--------------------------
私有方法 method1 被调用,无参无返回值
method2 被调用,有参有返回值,参数为任天蓬
method2 返回值为:任天蓬 fastoder...
*/

到此为止,有关 Java 的一些简单的反射技术,已经介绍完毕,以上代码都经过测试无误,希望能够对大家有所帮助。



相关