十分钟搞清 Java 反射

前言

因为后续在开发 Xposed 模块的时候,发现需要 Java 反射的概念,这几年没怎么接触反射的概念,就花点时间回忆下反射基础内容。

反射是 Java 的一种机制,可以在运行时获取类的信息作用,通过反射可以在程序运行时动态创建对象,还能获取到类的所有信息,比如它的属性、构造器、方法、注解等。

正射和反射

如果有反射那肯定有所谓的“正射”,正射就是在平常开发中知道的类,并且去创建实例化它:

public class Apple {

    private int price;
    public int publicPrice;
    private int privatePrice;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

比如上面的 Apple 类直接实例化:

public class Main {
    public static void main(String[] args) throws Exception {
        Apple apple = new Apple();
        apple.setPrice(4);
        System.out.println("正射:" + apple.getPrice());
    }
}

这个过程就是所谓的正射,那反射指的是在运行情况下通过字符串值知道要运行的类,然后获取类的构造且调用对应的方法:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取类的 Class 对象实例,根据包名路径走,例如(com.iiong.Apple)
        Class<?> clazz = Class.forName("Apple");
        // 根据 Class 对象实例获取 Constructor 对象
        Constructor<?> appleConstructor = clazz.getConstructor();
        // 使用 Constructor 对象的 newInstance 方法获取反射类对象
        Object appleObj = appleConstructor.newInstance();
        // 获取方法的 Method 对象
        Method setPriceMethod = clazz.getMethod("setPrice", int.class);
        // 利用 invoke 方法调用方法
        setPriceMethod.invoke(appleObj, 5);
        // 获取方法的 Method 对象
        Method getPriceMethod = clazz.getMethod("getPrice");
        // 输出利用 invoke 方法调用方法的数据
        System.out.println("反射:" + getPriceMethod.invoke(appleObj));
    }
}

最后输出的结果和正射代码是一样的,到此为止对反射掌握基本使用了,反射的应用场景:

  • Spring 实例化对象:当程序启动时,Spring 会读取配置文件applicationContext.xml并解析出里面所有的标签实例化到IOC容器中。
  • 反射 + 工厂模式:通过反射消除工厂中的多个分支,如果需要生产新的类,无需关注工厂类,工厂类可以应对各种新增的类,反射可以使得程序更加健壮。
  • JDBC连接数据库:使用JDBC连接数据库时,指定连接数据库的驱动类时用到反射加载驱动类。
  • ...

扩展

获取 Class 对象有三种方式,分别如下:

  • 根据类名:Apple.class
Class clazz = Apple.class;
  • 根据对象:new Apple().getClass()
Apple apple = new Apple();
Class clazz = apple.getClass();
  • 根据全限定类名:Class.forName("com.iiong.Apple"),也是本案例使用的方法

通过反射创建类对象主要通过俩个方式:

  • 通过 Class 对象的 newInstance() 方法
Class clazz = Apple.class;
Apple apple = (Apple)clazz.getDeclaredConstructor().newInstance();
  • 通过 Constructor 对象的 newInstance() 方法,也是本案例使用的方法
Class clazz = Apple.class;
Constructor<?> appleConstructor = clazz.getConstructor();
Apple apple = appleConstructor.newInstance();

同样我们可以通过 Class 对象的 getFields() 方法获取 Class 类的属性,但无法获取私有属性。

Class clazz = Apple.class;
Field[] fields = clazz.getFields();
for (Field field : fields) {
    System.out.println("Apple 类属性:" + field.getName());
}

打印的时候发现 private 声明的属性不会输出,所以这时候需要使用 getDeclaredFields 方法:

Field[] privateFields = clazz.getDeclaredFields();
for (Field field : privateFields) {
    System.out.println("Apple 类属性:" + field.getName());
}

与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。

方法名 说明
forName() - 获取 Class 对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类
- 为了产生 Class 引用,forName() 立即就进行了初始化
Object-getClass() 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用
getName() 取全限定的类名(包括包名),即类的完整名字
getSimpleName() 获取类名(不包括包名)
getCanonicalName() 获取全限定的类名(包括包名)
isInterface() 判断 Class 对象是否是表示一个接口
getInterfaces() 返回 Class 对象数组,表示Class对象所引用的类所实现的所有接口
getSupercalss() 返回 Class 对象,表示 Class 对象所引用的类所继承的直接基类,应用该方法可在运行时发现一个对象完整的继承结构
newInstance() 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器
getFields() 获得某个类的所有的公共 public 的字段,包括继承自父类的所有公共字段。 类似的还有 getMethodsgetConstructors
getDeclaredFields 获得某个类的自己声明的字段,即包括 publicprivateproteced ,默认但是不包括父类声明的任何字段。类似的还有 getDeclaredMethodsgetDeclaredConstructors