20. Java 使用反射(Reflection)-和内省技术
反射(Reflection)是程序的自我分析能力,通过反射可以确定类有哪些方法、有哪些构造方法以及有哪些成员变量。Java 语言提供了反射机制,通过反射机制能够动态读取一个类的信息;能够在运行时动态加载类,而不是在编译期。反射可以应用于框架开发,它能够从配置文件中读取配置信息动态加载类、创建对象,以及调用方法和成员变量。
Java 反射机制 API
Java 反射机制 API 主要是 java.lang.Class
类和 java.lang.reflect
包。
java.lang.Class 类
java.lang.Class 类是实现反射的关键所在,Class 类的一个实例表示 Java 的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和 void,void 是“无类型”,主要用于方法返回值类型声明,表示不需要返回值。Class 没有公有的构造方法,Class 实例是由 JVM 在类加载时自动创建的。
方法1:调用 Object 类的 getClass()方法。
方法2:使用 Class 类的 forName()方法。
方法3:如果 T 是一个 Java 类型,那么 T.class 就代表了与该类型匹配的 Class 对象。
1 | public static void main( String[] args ) throws ClassNotFoundException { |
每一种类型包括类和接口等,都有一个 class 静态变量可以获得 Class 实例。另外,每一个对象都有 getClass() 方法可以获得 Class 实例,该方法是由 Object 类提供的实例方法。
是否为接口: isInterface()
是否为数组对象: isArray()
是否是基本类型: isPrimitive()
获得父类: class: getSuperclass()
java.lang.reflect 包
java.lang.reflect 包提供了反射中用到类,主要的类说明如下:
- Constructor 类:提供类的构造方法信息。
- Field类:提供类或接口中成员变量信息。
- Method 类:提供类或接口成员方法信息。
- Array 类:提供了动态创建和访问 Java 数组的方法。
- Modifier 类:提供类和成员访问修饰符信息。
示例代码如下:
1 | public static void main(String[] args) { |
- 通过 Class 的静态方法 forName(String)创建某个类的运行时对象,其中的参数是类全名字符串,如果在类路径中找不到这个类则抛出 ClassNotFoundException 异常。
- 通过 Class 的实例方法 getDeclaredMethods()返回某个类的成员方法对象数组。
- method.getModifiers()方法返回访问权限修饰符常量代码,是int类型,例如 1 代表 public,这些数字代表的含义可以通过Modifier.toString(int)方法转换为字符串。
- 通过 Method 的 getReturnType()方法获得方法返回值类型,然后再调用 getName()方法返回该类型的名称。
- method.getName()返回方法名称。
创建对象
反射机制提供了另外一种创建对象方法,Class 类提供了一个实例方法 newInstance(),通过该方法可以创建对象。
下面两条语句实现了创建字符串 String 对象。
1 | Class clz = Class.forName("java.lang.String"); |
这两条语句相当于 String str = new String();
语句。另外,需要注意 newInstance() 方法可以会抛出
- InstantiationException 表示不能实例化异常
- IllegalAccessException 是不能访问构造方法异常。
调用构造方法
调用方法 newInstance() 创建对象,这个过程中需要调用构造方法,上面的代码只是调用了 String 的默认构造方法。如果想要调用非默认构造方法,需要使用 Constructor 对象,它对应着一个构造方法,获得 Constructor 对象需要使用Class 类的如下方法:
- Constructor[] getConstructors():返回所有公有构造方法 Constructor 对象数组。
- Constructor[] getDeclaredConstructors():返回所有构造方法 Constructor 对象数组。
- Constructor getConstructor(Class… parameterTypes):根据参数列表返回一个公有Constructor对象。参数 parameterTypes 是 Class数组,指定构造方法的参数列表。
- Constructor getDeclaredConstructor(Class… parameterTypes):根据参数列表返回一个Constructor对象。参数 parameterTypes 同上。
1 | private static void printConstructor() throws Exception { |
Java 反射机制能够在运行时动态加载类,而不是在编译期。在一些框架开发中经常将要实例化的类名保存到配置文件中,在运行时从配置文件中读取类名字符串,然后动态创建对象,建立依赖关系。采用 new 创建对象依赖关系是在编译期建立的,反射机制能够将依赖关系推迟到运行时建立,这种依赖关系动态注入进来称为依赖注入。
调用方法
通过反射机制还可以调用方法,这与调用构造方法类似。调用方法需要使用 Method 对象,它对应着一个方法,获得 Method 对象需要使用 Class 类的如下方法:
- Method[] getMethods():返回所有公有方法 Method 对象数组。
- Method[] getDeclaredMethods():返回所有方法 Method 对象数组。
- Method getMethod(String name, Class… parameterTypes):通过方法名和参数类型返回公有方法 Method 对象。参数parameterTypes是Class数组,指定方法的参数列表。
- Method getDeclaredMethod(String name, Class… parameterTypes):通过方法名和参数类型返回方法 Method 对象。参数 parameterTypes 同上。
1 | private static void testMethods() throws Exception { |
调用成员变量
通过反射机制还可以调用成员变量,调用方法需要使用 Field 对象,它对应着一个方法,获得 Field 对象需要使用 Class 类的如下方法:
- Field[] getFields():返回所有公有成员变量 Field 对象数组。
- Field[] getDeclaredFields():返回所有成员变量 Field 对象数组。
- Field getField(String name):通过指定公共成员变量名返回 Field 对象。
- Field getDeclaredField(String name):通过指定成员变量名返回 Field 对象。
1 | private static void testFiled() throws Exception { |
输出
1 | hello |
设置成员变量 accessible 标志为 true,accessible 是可访问性标志,值为 true 则指示反射的对象在使用时应该取消Java语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。不仅是成员变量,方法和构造方法也可以通过 setAccessible(true) 设置,实现对私有方法和构造方法的访问。
拓展
1.Type[] java.lang.Class.getGenericInterfaces()
2.Class<?>[] java.lang.Class.getInterfaces()
- 获取类的接口实现信息
1.返回实现接口信息的Type数组,包含泛型信息
2.返回实现接口信息的Class数组,不包含泛型信息
细看一下,就会发现其中端倪,当你的实现接口中不包含泛型时,同样调用1方法,其返回的接口信息必然不带泛型信息的,也就是 1 中包含 2。
如何拿到接口中定义的泛型 Person?
1 | public static void main(String[] args) { |
内省技术
PropertyDescriptor 类表示 JavaBean 类通过存储器导出一个属性。主要方法:
1、getPropertyType(),获得属性的 Class 对象。
2、getReadMethod(),获得用于读取属性值的方法;getWriteMethod(),获得用于写入属性值的方法。
3、hashCode(),获取对象的哈希值。
4、setReadMethod(Method readMethod),设置用于读取属性值的方法;setWriteMethod(Method writeMethod),设置用于写入属性值的方法;
将 JavaBean 中的属性封装起来进行操作。在程序把一个类当做 JavaBean 来看,就是调用 Introspector.getBeanInfo()方法,得到的 BeanInfo 对象封装了把这个类当做JavaBean看的结果信息,即属性的信息。需要导包 java.beans.*。
1 | private static void testPropertyDescriptor() throws Exception { |
遇到过的问题
NoSuchFieldException异常原因
①没有对应字段;
②属性为私有时获取Field用的方法不是getDeclaredField。
class package.XXX cannot access a member of class YYY with modifiers “”
或者错误日志 class package.XXX cannot access a member of class YYY with modifiers “private”
在 method.invoke 方法或者 field.get 方法前加上 clazz.setAccessible(true);
Field.getGenericType()/getType()方法的功能及区别说明
Field.getGenericType()/getType()方法的功能:
- getType():返回属性声明时类型对象(返回 class 对象)
- getGenericType():返回属性声的 Type 类型
getType() 和 getGenericType()的不同之处:
- 两者返回类型不同, 一个是 Class 对象一个是 Type 接口
- 当属性是一个泛型, 从 getType()只能得到这个属性的接口类型
从 getGenericType()还能得到这个泛型的参数类型。 - getGenericType()
当前属性有签名属性类型就返回,否则就返回 Field.getType()
Type 是 Java 编程语言中所有类型的通用超级接口。这些类型包括原始类型、参数化类型、数组类型、类型变量和基元类型。