前言
在 Java 中, 反射机制 (Reflection) 非常重要, 但对于很多开发者来说, 这并不容易理解, 甚至觉得有点神秘
今天, 我将献上一份 Java 反射机制的介绍 & 实战攻略, 希望你们会喜欢.
目录
Java 反射机制
1. 简介
定义: Java 语言中 一种 动态 (运行时) 访问, 检测 & 修改它本身的能力
作用: 动态 (运行时) 获取类的完整结构信息 & 调用对象的方法
类的结构信息包括: 变量, 方法等
正常情况下, Java 类在编译前, 就已经被加载到 JVM 中; 而反射机制使得程序运行时还可以动态地去操作类的变量, 方法等信息
2. 特点
2.1 优点
灵活性高. 因为反射属于动态编译, 即只有到运行时才动态创建 & 获取对象实例.
编译方式说明:
静态编译: 在编译时确定类型 & 绑定对象. 如常见的使用 new 关键字创建对象
动态编译: 运行时确定类型 & 绑定对象. 动态编译体现了 Java 的灵活性, 多态特性 & 降低类之间的藕合性
2.2 缺点
执行效率低
因为反射的操作 主要通过 JVM 执行, 所以时间成本会 高于 直接执行相同操作
因为接口的通用性, Java 的 invoke 方法是传 object 和 object[]数组的. 基本类型参数需要装箱和拆箱, 产生大量额外的对象和内存开销, 频繁促发 GC.
编译器难以对动态调用的代码提前做优化, 比如方法内联.
反射需要按名检索类和方法, 有一定的时间开销.
容易破坏类结构
因为反射操作饶过了源码, 容易干扰类原有的内部逻辑
3. 应用场景
动态获取 类文件结构信息(如变量, 方法等) & 调用对象的方法
常用的需求场景有: 动态代理, 工厂模式优化, Java JDBC 数据库操作等
下文会用实际例子详细讲解
4. 具体使用
4.1 Java 反射机制提供的功能
示意图
4.2 实现手段
反射机制的实现 主要通过 操作 java.lang.Class 类
下面将主要讲解 java.lang.Class 类
4.2.1 java.lang.Class 类
定义: java.lang.Class 类是反射机制的基础
作用: 存放着对应类型对象的 运行时信息
在 Java 程序运行时, Java 虚拟机为所有类型维护一个 java.lang.Class 对象
该 Class 对象存放着所有关于该对象的 运行时信息
泛型形式为 Class<T>
每种类型的 Class 对象只有 1 个 = 地址只有 1 个
- // 对于 2 个 String 类型对象, 它们的 Class 对象相同
- Class c1 = "Carson".getClass();
- Class c2 = Class.forName("java.lang.String");
- // 用 == 运算符实现两个类对象地址的比较
- System.out.println(c1 ==c2);
- // 输出结果: true
Java 反射机制的实现除了依靠 Java.lang.Class 类, 还需要依靠: Constructor 类, Field 类, Method 类, 分别作用于类的各个组成部分:
示意图
4.3 使用步骤
在使用 Java 反射机制时, 主要步骤包括:
获取 目标类型的 Class 对象
通过 Class 对象分别获取 Constructor 类对象, Method 类对象 & Field 类对象
通过 Constructor 类对象, Method 类对象 & Field 类对象分别获取类的构造函数, 方法 & 属性的具体信息, 并进行后续操作
下面, 我将详细讲解每个步骤中的使用方法.
步骤 1: 获取 目标类型的 Class 对象
- // 获取 目标类型的 `Class` 对象的方式主要有 4 种
- <-- 方式 1:Object.getClass() -->
- // Object 类中的 getClass()返回一个 Class 类型的实例
- Boolean carson = true;
- Class<?> classType = carson.getClass();
- System.out.println(classType);
- // 输出结果: class java.lang.Boolean
- <-- 方式 2:T.class 语法 -->
- // T = 任意 Java 类型
- Class<?> classType = Boolean.class;
- System.out.println(classType);
- // 输出结果: class java.lang.Boolean
- // 注: Class 对象表示的是一个类型, 而这个类型未必一定是类
- // 如, int 不是类, 但 int.class 是一个 Class 类型的对象
- <-- 方式 3:static method Class.forName -->
- Class<?> classType = Class.forName("java.lang.Boolean");
- // 使用时应提供异常处理器
- System.out.println(classType);
- // 输出结果: class java.lang.Boolean
- <-- 方式 4:TYPE 语法 -->
- Class<?> classType = Boolean.TYPE;
- System.out.println(classType);
- // 输出结果: boolean
此处额外讲一下 java.lang.reflect.Type 类
java.lang.reflect.Type
是 Java 中所有类型的父接口
这些类型包括:
示意图
之间的关系如下
示意图
步骤 2: 通过 Class 对象分别获取 Constructor 类对象, Method 类对象 & Field 类对象
- // 即以下方法都属于 `Class` 类的方法.
- <-- 1. 获取类的构造函数(传入构造函数的参数类型)->>
- // a. 获取指定的构造函数 (公共 / 继承)
- Constructor<T> getConstructor(Class<?>... parameterTypes)
- // b. 获取所有的构造函数(公共 / 继承)
- Constructor<?>[] getConstructors();
- // c. 获取指定的构造函数 ( 不包括继承)
- Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
- // d. 获取所有的构造函数( 不包括继承)
- Constructor<?>[] getDeclaredConstructors();
- // 最终都是获得一个 Constructor 类对象
- // 特别注意:
- // 1. 不带 "Declared" 的方法支持取出包括继承, 公有 (Public) & 不包括有(Private) 的构造函数
- // 2. 带 "Declared" 的方法是支持取出包括公共 (Public), 保护(Protected), 默认(包) 访问和私有 (Private) 的构造方法, 但不包括继承的构造函数
- // 下面同理
- <-- 2. 获取类的属性(传入属性名) -->
- // a. 获取指定的属性(公共 / 继承)
- Field getField(String name) ;
- // b. 获取所有的属性(公共 / 继承)
- Field[] getFields() ;
- // c. 获取指定的所有属性 (不包括继承)
- Field getDeclaredField(String name) ;
- // d. 获取所有的所有属性 (不包括继承)
- Field[] getDeclaredFields() ;
- // 最终都是获得一个 Field 类对象
- <-- 3. 获取类的方法(传入方法名 & 参数类型)-->
- // a. 获取指定的方法(公共 / 继承)
- Method getMethod(String name, Class<?>... parameterTypes) ;
- // b. 获取所有的方法(公共 / 继承)
- Method[] getMethods() ;
- // c. 获取指定的方法 ( 不包括继承)
- Method getDeclaredMethod(String name, Class<?>... parameterTypes) ;
- // d. 获取所有的方法( 不包括继承)
- Method[] getDeclaredMethods() ;
- // 最终都是获得一个 Method 类对象
- <-- 4. Class 类的其他常用方法 -->
- getSuperclass();
- // 返回父类
- String getName();
- // 作用: 返回完整的类名(含包名, 如 java.lang.String )
- Object newInstance();
- // 作用: 快速地创建一个类的实例
- // 具体过程: 调用默认构造器(若该类无默认构造器, 则抛出异常
- // 注: 若需要为构造器提供参数需使用 java.lang.reflect.Constructor 中的 newInstance()
步骤 3: 通过 Constructor 类对象, Method 类对象 & Field 类对象分别获取类的构造函数, 方法 & 属性的具体信息 & 进行操作
- // 即以下方法都分别属于 `Constructor` 类,`Method` 类 & `Field` 类的方法.
- <-- 1. 通过 Constructor 类对象获取类构造函数信息 -->
- String getName();// 获取构造器名
- Class getDeclaringClass();// 获取一个用于描述类中定义的构造器的 Class 对象
- int getModifiers();// 返回整型数值, 用不同的位开关描述访问修饰符的使用状况
- Class[] getExceptionTypes();// 获取描述方法抛出的异常类型的 Class 对象数组
- Class[] getParameterTypes();// 获取一个用于描述参数类型的 Class 对象数组
- <-- 2. 通过 Field 类对象获取类属性信息 -->
- String getName();// 返回属性的名称
- Class getDeclaringClass(); // 获取属性类型的 Class 类型对象
- Class getType();// 获取属性类型的 Class 类型对象
- int getModifiers(); // 返回整型数值, 用不同的位开关描述访问修饰符的使用状况
- Object get(Object obj) ;// 返回指定对象上 此属性的值
- void set(Object obj, Object value) // 设置 指定对象上此属性的值为 value
- <-- 3. 通过 Method 类对象获取类方法信息 -->
- String getName();// 获取方法名
- Class getDeclaringClass();// 获取方法的 Class 对象
- int getModifiers();// 返回整型数值, 用不同的位开关描述访问修饰符的使用状况
- Class[] getExceptionTypes();// 获取用于描述方法抛出的异常类型的 Class 对象数组
- Class[] getParameterTypes();// 获取一个用于描述参数类型的 Class 对象数组
- <-- 额外: java.lang.reflect.Modifier 类 -->
- // 作用: 获取访问修饰符
- static String toString(int modifiers)
- // 获取对应 modifiers 位设置的修饰符的字符串表示
- static boolean isXXX(int modifiers)
- // 检测方法名中对应的修饰符在 modifiers 中的值
至此, 关于 Java 反射机制的步骤说明已经讲解完毕.
4.4 特别注意: 访问权限问题
背景
反射机制的默认行为受限于 Java 的访问控制
如, 无法访问 ( private ) 私有的方法, 字段
冲突
Java 安全机制只允许查看任意对象有哪些域, 而不允许读它们的值
若强制读取, 将抛出异常
解决方案
脱离 Java 程序中安全管理器的控制, 屏蔽 Java 语言的访问检查, 从而脱离访问控制
具体实现手段: 使用 Field 类, Method 类 & Constructor 类对象的 setAccessible()
- void setAccessible(boolean flag)
- // 作用: 为反射对象设置可访问标志
- // 规则: flag = true 时 , 表示已屏蔽 Java 语言的访问检查, 使得可以访问 & 修改对象的私有属性
- boolean isAccessible()
- // 返回反射对象的可访问标志的值
- static void setAccessible(AccessibleObject[] array, boolean flag)
- // 设置对象数组可访问标志
5. 实例应用讲解
5.1 基础应用讲解
实例 1: 利用反射获取类的属性 & 赋值
- <-- 测试类定义 -->
- public class Student {
- public Student() {
- System.out.println("创建了一个 Student 实例");
- }
- private String name;
- }
- <-- 利用反射获取属性 & 赋值 -->
- // 1. 获取 Student 类的 Class 对象
- Class studentClass = Student.class;
- // 2. 通过 Class 对象创建 Student 类的对象
- Object mStudent = studentClass.newInstance();
- // 3. 通过 Class 对象获取 Student 类的 name 属性
- Field f = studentClass.getDeclaredField("name");
- // 4. 设置私有访问权限
- f.setAccessible(true);
- // 5. 对新创建的 Student 对象设置 name 值
- f.set(mStudent, "Carson_Ho");
- // 6. 获取新创建 Student 对象的的 name 属性 & 输出
- System.out.println(f.get(mStudent));
测试结果
image.PNG
Demo 地址
Carson_Ho 的 GitHub 地址: Reflect_Demo1
实例 2: 利用反射调用类的构造函数
- <-- 测试类定义 -->
- public class Student {
- // 无参构造函数
- public Student() {
- System.out.println("调用了无参构造函数");
- }
- // 有参构造函数
- public Student(String str) {
- System.out.println("调用了有参构造函数");
- }
- private String name;
- }
- <-- 利用反射调用构造函数 -->
- // 1. 获取 Student 类的 Class 对象
- Class studentClass studentClass = Student.class;
- // 2.1 通过 Class 对象获取 Constructor 类对象, 从而调用无参构造方法
- // 注: 构造函数的调用实际上是在 newInstance(), 而不是在 getConstructor()中调用
- Object mObj1 = studentClass.getConstructor().newInstance();
- // 2.2 通过 Class 对象获取 Constructor 类对象(传入参数类型), 从而调用有参构造方法
- Object mObj2 = studentClass.getConstructor(String.class).newInstance("Carson");
测试结果
示意图
Demo 地址
Carson_Ho 的 GitHub 地址: Reflect_Demo2
实例 3: 利用反射调用类对象的方法
- <-- 测试类定义 -->
- public class Student {
- public Student() {
- System.out.println("创建了一个 Student 实例");
- }
- // 无参数方法
- public void setName1 (){
- System.out.println("调用了无参方法: setName1()");
- }
- // 有参数方法
- public void setName2 (String str){
- System.out.println("调用了有参方法 setName2(String str):" + str);
- }
- }
- <-- 利用反射调用方法 -->
- // 1. 获取 Student 类的 Class 对象
- Class studentClass = Student.class;
- // 2. 通过 Class 对象创建 Student 类的对象
- Object mStudent = studentClass.newInstance();
- // 3.1 通过 Class 对象获取方法 setName1()的 Method 对象: 需传入方法名
- // 因为该方法 = 无参, 所以不需要传入参数
- Method msetName1 = studentClass.getMethod("setName1");
- // 通过 Method 对象调用 setName1(): 需传入创建的实例
- msetName1.invoke(mStudent);
- // 3.2 通过 Class 对象获取方法 setName2()的 Method 对象: 需传入方法名 & 参数类型
- Method msetName2 = studentClass.getMethod("setName2",String.class);
- // 通过 Method 对象调用 setName2(): 需传入创建的实例 & 参数值
- msetName2.invoke(mStudent,"Carson_Ho");
测试结果
示意图
Demo 地址
Carson_Ho 的 GitHub 地址: Reflect_Demo3
5.2 常见需求场景讲解
实例 1: 工厂模式优化
背景
采用简单工厂模式
冲突
操作成本高: 每增加一个接口的子类, 必须修改工厂类的逻辑
系统复杂性提高: 每增加一个接口的子类, 都必须向工厂类添加逻辑
关于 简单工厂模式的介绍 & 使用 请看文章: 简单工厂模式(SimpleFactoryPattern)- 最易懂的设计模式解析
解决方案
采用反射机制: 通过 传入子类名称 & 动态创建子类实例, 从而使得在增加产品接口子类的情况下, 也不需要修改工厂类的逻辑
实例演示
步骤 1. 创建抽象产品类的公共接口
Product.java
abstract class Product{ public abstract void show(); }
步骤 2. 创建具体产品类(继承抽象产品类), 定义生产的具体产品
<-- 具体产品类 A:ProductA.java --> public class ProductA extends Product{ @Override public void show() { System.out.println("生产出了产品 A"); } } <-- 具体产品类 B:ProductB.java --> public class ProductB extends Product{ @Override public void show() { System.out.println("生产出了产品 B"); } }
步骤 3. 创建工厂类
Factory.java
public class Factory { // 定义方法: 通过反射动态创建产品类实例 public static Product getInstance(String ClassName) { Product concreteProduct = null; try { // 1. 根据 传入的产品类名 获取 产品类类型的 Class 对象 Class product_Class = Class.forName(ClassName); // 2. 通过 Class 对象动态创建该产品类的实例 concreteProduct = (Product) product_Class.newInstance(); } catch (Exception e) { e.printStackTrace(); } // 3. 返回该产品类实例 return concreteProduct; } }
步骤 4: 外界通过调用工厂类的静态方法(反射原理), 传入不同参数从而创建不同具体产品类的实例
TestReflect.java
public class TestReflect { public static void main(String[] args) throws Exception { // 1. 通过调用工厂类的静态方法(反射原理), 从而动态创建产品类实例 // 需传入完整的类名 & 包名 Product concreteProduct = Factory.getInstance("scut.carson_ho.reflection_factory.ProductA"); // 2. 调用该产品类对象的方法, 从而生产产品 concreteProduct.show(); } }
展示结果
示意图
Demo 地址
Carson_Ho 的 GitHub 地址: Reflection_Factory1
如此一来, 通过采用反射机制(通过 传入子类名称 & 动态创建子类实例), 从而使得在增加产品接口子类的情况下, 也不需要修改工厂类的逻辑 & 增加系统复杂度
实例 2: 应用了反射机制的工厂模式再次优化
背景
在上述方案中, 通过调用工厂类的静态方法(反射原理), 从而动态创建产品类实例(该过程中: 需传入完整的类名 & 包名)
冲突
开发者 无法提前预知 接口中的子类类型 & 完整类名
解决方案
通过 属性文件的形式( Properties) 配置所要的子类信息, 在使用时直接读取属性配置文件从而获取子类信息(完整类名)
具体实现
步骤 1: 创建抽象产品类的公共接口
Product.java
abstract class Product{ public abstract void show(); }
步骤 2. 创建具体产品类(继承抽象产品类), 定义生产的具体产品
<-- 具体产品类 A:ProductA.java --> public class ProductA extends Product{ @Override public void show() { System.out.println("生产出了产品 A"); } } <-- 具体产品类 B:ProductB.java --> public class ProductB extends Product{ @Override public void show() { System.out.println("生产出了产品 B"); } }
步骤 3. 创建工厂类
Factory.java
public class Factory { // 定义方法: 通过反射动态创建产品类实例 public static Product getInstance(String ClassName) { Product concreteProduct = null; try { // 1. 根据 传入的产品类名 获取 产品类类型的 Class 对象 Class product_Class = Class.forName(ClassName); // 2. 通过 Class 对象动态创建该产品类的实例 concreteProduct = (Product) product_Class.newInstance(); } catch (Exception e) { e.printStackTrace(); } // 3. 返回该产品类实例 return concreteProduct; } }
步骤 4: 创建属性配置文件
Product.properties // 写入抽象产品接口类的子类信息(完整类名) ProductA = scut.carson_ho.reflection_factory.ProductA ProductB = scut.carson_ho.reflection_factory.ProductB
步骤 5: 将属性配置文件 放到 src/main/assets 文件夹中
若没 assets 文件夹, 则自行创建
步骤 6: 在动态创建产品类对象时, 动态读取属性配置文件从而获取子类完整类名
TestReflect.java
public class TestReflect { public static void main(String[] args) throws Exception { // 1. 读取属性配置文件 Properties pro = new Properties() ; pro.load(this.getAssets().open("Product.properties")); // 2. 获取属性配置文件中的产品类名 String Classname = pro.getProperty("ProductA"); // 3. 动态生成产品类实例 Product concreteProduct = Factory.getInstance(Classname); // 4. 调用该产品类对象的方法, 从而生产产品 concreteProduct.show(); }
测试结果
示意图
Demo 地址
Carson_Ho 的 GitHub 地址: Reflection_Factory2
实例 3: 动态代理
通过反射机制实现动态代理, 具体请看文章
6. 总结
本文全面讲解了 Java 反射机制 (Reflection) 的相关知识, 相信您对 Java 反射机制已经非常了解
下面我将继续对 Android 中的知识进行深入讲解 , 有兴趣可以继续关注 Carson_Ho 的安卓开发笔记
请点赞! 因为你的鼓励是我写作的最大动力!
来源: http://www.jianshu.com/p/356e1d7a9d11