跳至主要內容

Java基础 - 反射机制

Zenghr大约 9 分钟Java

Java 基础 - 反射机制

提示

本文主要记录学习 Java 反射(reflect)的一点心得,在了解反射之前,应该先了解 Java 中的 Class 类

带着BAT大厂的面试问题去理解

  • Java 反射的作用?
  • 哪里会用到反射?
  • 获取 Class 对象有几种方式?
  • 反射的实现方式有哪些?
  • 利用反射动态创建对象实例?

什么是反射机制

提示

Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种 动态的获取信息 以及 动态调用对象的方法 的功能称为 java 的反射机制

Class(字节码对象)

Class 是 java.lang 包里的一个类,在 Java 中,学习面向对象时,我们通常把多个事物,发现他们的共性特征,抽出来写成一个 Java 类,可以根据该类创建具体的个体对象,编译时会生成一个个 .class 后缀的字节码文件。

在学习 JVM内存模型时 Java基础 - Jvm内存模型 ,里面有一个 方法区(Method Area) 是用于存储 类信息、常量、静态变量等数据,当我们的程序在运行时,用到某个类例如 Person 类,Jvm 会先扫描然后通过类加载器将 Person 的类信息存储到 方法区(Method Area) 中,再通过抽取 Person.class 字节码文件的模板来创建 Class 实例,流程如下👇

mark
mark

所以我们也可以认为一个 .class 字节码文件就是一个 Class 对象,有且只有一个对应的 Class 对象

获取字节码对象的三种方式

通过 API ,我们得知 Class ,没有公共的构造器,其原因是 Class 对象是在加载类时由 Java 虚拟机自动构造的

// 1. 通过字符串获取 Class 对象,称为 全限定名
Class clz1 = Class.forName("cn.zenghr.reflect.User");
// 2. 通过类的 class 属性
Class clz2 = User.class;
// 3. 通过对象的 getClass() 函数
User user = new User();
Class clz3 = user.getClass();
  • 第一种方法是通过类的 全限定名 获取 Class 对象,这也是我们平时最常用的反射获取 Class 对象的方法;
  • 第二种方法有限制条件:需要导入类的包;
  • 第三种方法已经有了 User 对象,不再需要反射

因为 Java 运行时,每一个类只会生成一个 Class 对象,有且只有一个,所有三种方式获取的 Class 对象是同一个

System.out.println(clz1 == clz2); // true
System.out.println(clz2 == clz3); // true

反射前言

提示

总结: API 方法中带着 s 表示获取多个,带着 Declared 表示忽略权限,包括私有的也可以获取到

User(演示类,以下代码都使用该类演示)

public class User {
    private String name;
    private int age;
    
    // 无参构造器
    public User() {}
    
    // 有参构造器
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 私有构造器
    private User(String name) {
        this.name = name;
    }

    // 公共构方法 - 无参
    public void show() {
        System.out.println("user - show");
    }

    // 私有方法 - 有参
    private void show(String msg) {
        System.out.println("user:" + msg);
    }

    // 公共静态方法 - 有参
    public static void show(int age) {
        System.out.println("user:" + age);
    }
    
    // seter / geter 省略

}

反射 API

Java 类的成员包括以下三类:属性字段、构造函数、方法。反射的 API 也是与这几个成员相关👇

mark
  • Fiele 类:提供有关类的属性信息,以及对它的动态访问权限。它是一个封装反射类的属性的类
  • Constructor 类:提供有关类的构造方法的信息,以及对它的动态访问权限。它是一个封装反射类的构造方法的类。
  • Method 类:提供关于类的方法的信息,包括抽象方法。它是用来封装反射类方法的一个类。
  • Class 类:表示正在运行的 Java 应用程序中的类的实例。
  • Object 类:Object 是所有 Java 类的父类。所有对象都默认实现了 Object 类的方法

newInstance()

Class 类中提供了通过无参构造器快速构建对象的方法 newInstance() ,注意:该方法自从 jdk1.9 之后被标记为 过时方法

AccessibleObject

AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力

对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获取字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查

当访问私有的属性、方法、构造器时,不取消权限控制会抛出异常:IllegalAccessException,具体实现方法

// 取消权限后记得还原权限
setAccessible(true); // 取消权限
setAccessible(false) // 还原权限

获取构造器(Constructor)

获取构造器的 API

  • getConstructors(): 获取所有公共的构造方法
  • getDeclaredConstructors(): 忽略权限,获取所有构造器包括私有的
  • getConstructor(Class... parameterTypes): 获取指定的公共构造器,args:参数列表的类型
  • getDeclaredConstructor(Class.. parameterTypes): 获取指定的构造器,args:参数列表的类型

Constructor 类常用方法:newInstance() 根据字节码生成一个对象

// 获取字节码对象
Class clz = User.class;

// 获取所有构造器 - 公共的
Constructor[] constructors = clz.getConstructors();
for (Constructor constructor : constructors) {
    System.out.println(constructor);
}

// 忽略权限 获取所有构造器 - declared
Constructor[] declaredConstructors = clz.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
    System.out.println(constructor);
}

// 获取指定的公共构造器 - 无参
Constructor constructor = clz.getConstructor();

// 忽略权限 获取指定的构造器 - 无参
Constructor privateConstructor = clz.getDeclaredConstructor(String.class);
// 获取指定的公共构造器 - 有参
Constructor constructor1 = clz.getConstructor(String.class, int.class);

获取方法(Method)

获取方法的 API

  • getMethods(): 获取公共的方法
  • getDeclaredMethods(): 忽略权限,获取所有方法包括私有的
  • getMethod(String name, Class<?>... parameterTypes): 获取指定的公共方法,args:方法名称、参数列表的类型
  • getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的方法,args:方法名称、参数列表的类型

Method 类中有个方法,可以调用执行方法 invoke(Object, Object... args),args:需要调用方法的对象实例、方法参数

// 获取字节码对象
Class clz = User.class;
// 获取所有公共方法
Method[] methods = clz.getMethods();
for (Method method : methods) {
    System.out.println(method);
}

// 获取所有方法 - 忽略权限
Method[] declaredMethods = clz.getDeclaredMethods();
for (Method method : declaredMethods) {
    System.out.println(method);
}
// 通过 Class 的 newInstance() 方法快速通过无参构造器构建一个对象
Object obj = clz.newInstance();
// 获取指定方法 - 公共的
Method show = clz.getMethod("show");
// 运行方法
show.invoke(obj);

// 反射获取方法 - 私有
Method show1 = clz.getDeclaredMethod("show", String.class);
// 取消权限
show1.setAccessible(true);
show1.invoke(obj, "hello");
show1.setAccessible(false);

// 反射获取静态方法 todo: 调用静态方法时 参数传入 null
Method show2 = clz.getDeclaredMethod("show", int.class);
show2.invoke(null, 25);

获取字段(Field)

获取字段的 API

  • getFields(): 获取公共的字段,(因为字段大部分是私有的,该方法不常用
  • getDeclaredFields(): 忽略权限,获取所有字段包括私有的
  • getField(String name): 获取指定的公共字段 - 不常用,args:字段名称
  • getDeclaredField(String name): 获取指定的字段,args:字段名称

Field 类 常用方法:

  • getName() - 获取字段名称
  • get() - 获取字段值
  • set() - 设置字段值
User user = new User("张三 ", 20);
// 获取字节码对象
Class clz = user.getClass();
Field name = clz.getDeclaredField("name");

name.setAccessible(true);
// 修改字段值
name.set(user, "李四");
name.setAccessible(false);

// 获取所有字段,普遍使用 getDeclaredFields()
Field[] fields = clz.getDeclaredFields();
// 遍历字段
for (Field field : fields) {
    // 设置私有字段可访问
    field.setAccessible(true);
    // 获取指定字段值
    System.out.println(field.getName() + "=" + field.get(user));
    // 恢复权限
    field.setAccessible(false);
}

内省 - Introspector

介绍

JavaBean是一个非常常用的组件,无外乎就是操作里面的属性.而之前咱们要获取JavaBean中的方法,如果使用反射非常麻烦.SUN公司专门提供了一套操作 Javabean 属性的API:内省(Introspector

内省的作用

  1. 获取到属性名和属性类型等相关状态信息.
  2. 获取属性对应的读写方法操作属性的值等操作方式.

内省相关类以及API

Introspector

Introspector 类似于BeanInfo的静态工厂类,主要是提供静态方法通过Class实例获取到BeanInfo,得到BeanInfo之后,就能够获取到其他描述符。主要方法:

  • public static BeanInfo getBeanInfo(Class<?> beanClass):通过Class实例获取到BeanInfo实例。

BeanInfo

BeanInfo 是一个接口,具体实现是 GenericBeanInfo,通过这个接口可以获取一个类的各种类型的描述符。主要方法:

  • BeanDescriptor getBeanDescriptor():获取JavaBean描述符。
  • EventSetDescriptor[] getEventSetDescriptors():获取JavaBean的所有的 EventSetDescriptor
  • PropertyDescriptor[] getPropertyDescriptors():获取JavaBean的所有的 PropertyDescriptor
  • MethodDescriptor[] getMethodDescriptors():获取JavaBean的所有的 MethodDescriptor

PropertyDescriptor

PropertyDescriptor 类表示JavaBean类通过存储器(SetterGetter)导出一个属性,它应该是内省体系中最常见的类。主要方法:

  • synchronized Class<?> getPropertyType():获得属性的Class对象。
  • synchronized Method getReadMethod():获得用于读取属性值(Getter)的方法;
  • synchronized Method getWriteMethod():获得用于写入属性值(Setter)的方法。
  • int hashCode():获取对象的哈希值。
  • synchronized void setReadMethod(Method readMethod):设置用于读取属性值(Getter)的方法。
  • synchronized void setWriteMethod(Method writeMethod):设置用于写入属性值(Setter)的方法

内省实战

提示

注意:getBeanInfo(Class beanClass, Class stopClass) 方法第二参数表示,不内省 stopClass 类,通常传入 Object.class

public static void main(String[] args) {
    try {
        // 给定字节码对象获取 beanInfo 对象
        BeanInfo beanInfo = Introspector.getBeanInfo(User.class, Object.class);

        // 获取 beanInfo 对象的信息
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

        // 循环遍历输出
        for (PropertyDescriptor pd : propertyDescriptors) {
            // 获取属性 name
            System.out.println(pd.getName() + "=" + pd.getPropertyType());
            // 获取可读方法
            Method readMethod = pd.getReadMethod();
            System.out.println(readMethod);

            Method writeMethod = pd.getWriteMethod();
            System.out.println(writeMethod);

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

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

输出

age=int
public int cn.zenghr.advanced.domain.User.getAge()
public void cn.zenghr.advanced.domain.User.setAge(int)
------------------------------
name=class java.lang.String
public java.lang.String cn.zenghr.advanced.domain.User.getName()
public void cn.zenghr.advanced.domain.User.setName(java.lang.String)

JavaBean to Map 实现

/**
 * java to map 互转
 */
public class BeanUtil {
    private BeanUtil() {
    }

    /**
     * JavaBean 内省 转 Map
     *
     * @param obj JavaBean
     * @return 返回 map
     */
    public static Map<Object, Object> bean2map(Object obj) {
        Map<Object, Object> map = new HashMap<>();

        try {
            // 1. 获取 beanInfo 对象
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass(), Object.class);

            // 2. 获取 beanInfo 的属性
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();


            // 3. 循环遍历获取属性 key-value
            for (PropertyDescriptor pd : propertyDescriptors) {
                String key = pd.getName();
                Object val = pd.getReadMethod().invoke(obj);

                // 添加
                map.put(key, val);
            }
        } catch (IntrospectionException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


        return map;
    }

    /**
     * 通过反射获取字段 转化 map
     * @param obj
     * @return
     */
    public static Map<Object, Object> bean2mapReflect(Object obj) {
        Map<Object, Object> map = new HashMap<>();

        try {
            Class clz = obj.getClass();

            Field[] fields = clz.getDeclaredFields();

            for (Field field : fields) {
                String key = field.getName();
                field.setAccessible(true);
                Object val = field.get(obj);
                field.setAccessible(false);

                map.put(key, val);
            }


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


        return map;
    }


    /**
     * map to javabean 泛型
     * @param map map数据
     * @param clz 类类型
     * @param <T> 泛型
     * @return 返回泛型对象
     */
    public static <T> T map2bean(Map<Object, Object> map, Class<T> clz) {
        try {
            // 获取 beanInfo 信息
            BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
            // 获取 bean 的属性
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            // 生成 泛型实例
            T t = clz.newInstance();

            for (PropertyDescriptor pd : propertyDescriptors) {
                String key = pd.getName();
                Object val = map.get(key);

                pd.getWriteMethod().invoke(t, val);
            }

            return t;
        } catch (IntrospectionException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


        return null;
    }
}