【Java基础】之深入讲解反射

这篇文章将详细介绍 Java 的反射机制

什么是反射?为什么要使用反射?


Java 反射机制是在运行状态中:

  • 对于任意一个类,都能够知道这个类的所有属性和方法;
  • 对于任意一个对象,都能够调用它的任意一个方法和属性;

这种动态获取类的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。通过使用反射我们可以很方便地动态创建对象、动态调用对象的方法和访问对象的成员变量。

反射更多的是应用在各类 Java 框架之中,如果想要深入了解框架知识,就必须扎实地掌握 Java 的反射机制。

反射机制的原理


我们先来了解一下与反射机制相关的类:

类名用途
java.lang.Class描述其他类的类
java.lang.reflect.Method描述类的方法
java.lang.reflect.Field描述类的成员变量
java.lang.reflect.Constructor描述类的构造方法

我们可以简单地理解为:Java 的反射机制是主要是通过操作这四个类来实现的。其中 Class 类是最根本、最核心的类,因为另外三个相关的类都是通过 Class 类的实例对象来获取的,换句话说,Class 类是 Java 反射的源头,反射的操作就是从创建 Class 实例对象开始的。下面我们就通过介绍 Class 类来一步步了解 Java 的反射操作。


Class 类


Class 类是用来描述其他类的类,也就是说,Class 类的每一个实例化对象就是对其他类的描述。

创建一个类所对应的 Class 对象的常用方式有:

  • 调用 Class 的静态方法 forName
  • 使用类的 .class 语法
  • 调用对象的 getClass() 方法

比如:

public static void main(String[] args) {
    try {
        // 通过调用 Class 的静态方法 forName 创建 Class 对象
        Class<?> strClass1 = Class.forName("java.lang.String");

        // 通过使用类的 .class 语法创建 Class 对象
        Class<?> strClass2 = String.class;

        // 通过调用对象的 getClass() 方法创建 Class 对象
        String str = "abc";
        Class<?> strClass3 = str.getClass();

    } catch (Exception e) {
        e.printStackTrace();
    } 
}
由于在 Object 类中定义了方法 public final native Class<?> getClass() 并且这个方法被所有子类继承,所以,每个类的实例对象都可以通过调用 getClass() 方法来获取对应的 Class 对象。

有了 Class 对象之后我们就可以进行各种各样的操作了,比如创建类的实例对象,获取类的方法和属性的信息,调用对象的方法以及设置对象的属性值等等。

📝 通过 Class 动态创建类的实例对象

使用 Class 创建对象的方式有两种:

  • 直接调用 Class 对象的 newInstance() 方法来创建类的实例对象
  • 通过 Class 获取类的构造器对象,然后再通过调用构造器对象的 newInstance() 方法来创建类的实例对象

比如:

public static void main(String[] args) {
    try {
        // 通过调用 Class 的静态方法 forName 创建 Class 对象
        Class<?> strClass1 = Class.forName("java.lang.String");

        // 通过调用 newInstance() 创建对象:已弃用
        String str1 = (String)strClass1.newInstance();

        // 先通过 Class 获取类的构造器对象,然后再调用构造器对象的 newInstance() 方法
        Constructor<?> strCon = strClass1.getConstructor();
        String str2 = (String)strCon.newInstance();

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

这里需要注意的是,第一种方式在新版 JDK 中已经弃用,不建议使用了,我们推荐第二种方式来动态创建对象。在第二种方式中,获取类的构造函数有四种方式:

  • getConstructor(Class<?>… parameterTypes):获取指定参数的公有构造方法
  • getDeclaredConstructor(Class<?>… parameterTypes):获取指定参数的构造方法
  • getConstructors():获取所有的公有构造方法
  • getDeclaredConstructors():获取所有构造方法

这里唯一需要区分的是:没有 declared 所获取的构造方法只能是公有的,而有 declared 所获取的是构造方法不限于公有的,私有的、保护的都可以。如果获取的是私有构造方法,则需要调用构造器的 setAccessible(true) 来设置访问属性为 true,这样才有权限调用 newInstance() 方法。

获取指定参数的构造方法提供的参数类型使用类的 .class 语法,个数是可选的,比如: getConstructor(String.class) 表示获取的构造方法包含一个 String 类型的参数,在实例化对象时就需要这样调用:newInstance(“hello”),类似的,getConstructor(String.class, String.class) 就需要这样调用:newInstance(“hello”, “Java”)。

📝 通过 Class 调用对象的方法

通过 Class 调用对象的方法的步骤为:

1、获取 Class 对象
2、调用 Class 的 getMethod() 方法获取一个 Method 对象
3、通过 Method 的 invoke() 方法调用对象的方法

比如:

public class Student {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public static void main(String[] args) {
        try {
            // 获取 Class 对象
            Student stu = new Student();
            Class<?> stuCls = stu.getClass();

            // 通过 Class 获取 Student 类的 setName 方法
            Method method = stuCls.getMethod("setName", String.class);

            // 调用 setName 方法
            method.invoke(stu, "Jack");

            System.out.println("Hello: " + stu.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

>>>>>
Hello: Jack

同样,获取 Method 对象也有四种方式:

  • getMethod(String name, Class<?>… parameterTypes):获取指定参数的公有方法
  • getDeclaredMethod(String name, Class<?>… parameterTypes):获取指定参数的方法
  • getMethods():获取所有公有方法
  • getDeclaredMethods():获取所有方法

这四种方式的区分方法和构造器一致:没有 Declare 只能获取公有的方法,而有 Declare 可以获取包括公有、私有等权限的方法。获取指定的方法第一个参数指定需要获取的方法名,后面的可选参数类型就是指定方法的参数类型和个数。如果获取的是私有方法,则需要设置访问属性为 true,比如:method.setAccessible(true),否则无法调用方法

我们注意到,在调用 Method 的 invoke 方法时,和我们平时调用方法的方式有点区别,除了需要提供调用方法的参数之外,还需提供调用这个方法所属的对象。

为什么要提供调用方法所属的对象呢?

原因是这样子的:Java 运行的时候,某个类无论生成多少个对象,他们都会对应同一个 Class 对象,它表示正在运行程序中的类和接口,所以在使用 invoke 方法时就需要明确指定到底调用的是哪个对象的方法。

📝 通过 Class 访问和设置对象属性

通过 Class 访问和设置对象属性的步骤为:

1、获取 Class 对象
2、调用 Class 的 getField() 方法获取 Field 对象
3、通过 Field 对象的 get() 方法获取属性对象,通过 set() 方法修改对象的属性

比如:

public class Student {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public static void main(String[] args) {
        try {
            // 获取 Class 对象
            Student stu = new Student("Jack");
            Class<?> stuCls = stu.getClass();

            // 通过 Class 获取 Field 对象
            Field field = stuCls.getDeclaredField("name");
            field.setAccessible(true);

            // 访问属性对象
            String name = (String) field.get(stu);
            System.out.println("Hello: " + name);

            // 设置属性对象
            field.set(stu, "Marry");
            System.out.println("Hello: " + stu.getName());

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

>>>>>
Hello: Jack
Hello: Marry

和获取 Method 对象一样,获取 Filed 也有四种方法:

  • getField(String name):获取指定的公有属性
  • getDeclaredField(String name):获取指定的属性
  • getFields():获取所有公有属性
  • getDeclaredFields():获取所有属性

区别和用途参考 Method,例子中因为属性 name 是 private 类型的,所以只能使用 getDeclaredField(),并且需要调用 setAccessible(true) 来设置属性的可访问性为 true。

这里需要注意的是:通过 Field 的 get() 方法返回的属性对象只是一个新拷贝的属性对象,修改它的值也只是修改临时变量的值,不会影响任何 Student 对象的 name 属性值。 和 Method 的 invoke() 方法一样,Field 的 set() 方法也需要指定设置的是哪个 Student 对象的 name 属性值。


总结


目前为止我们已经介绍完了,Java 反射的基本原理和用法,说白了就是在 Java 程序运行过程中,动态地创建类的实例对象、调用类实例对象的方法、访问和修改对象的属性。通过反射我们可以很清楚的知道一个类的内部结构(包含哪些方法和属性)。并且我们也知道了,不管一个类有多少个实例对象,都只能有一个对应的 Class 对象,所以通过 Class 对象获取的 Method、Field 对象在调用 invoke、set 方法时需要提供调用的对象。

Java 的反射机制就是通过操作 Class 类、Method 类、Filed 类和 Constructor 类这四个的类的各个方法来实现的,本篇文章只列出了少量常用的方法,如果想要了解更多的话,可以查看它们的其它方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值