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 实例,流程如下👇
所以我们也可以认为一个 .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 也是与这几个成员相关👇
- 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)
内省的作用
- 获取到属性名和属性类型等相关状态信息.
- 获取属性对应的读写方法操作属性的值等操作方式.
内省相关类以及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
类通过存储器(Setter
和Getter
)导出一个属性,它应该是内省体系中最常见的类。主要方法:
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;
}
}