本文摘自:GitYuan:理解Java反射机制

概述

JAVA反射机制,可在运行态直接操作任意类或对象的所有属性和方法,主要有以下几个功能:

  • 在运行时获取任意对象所属的类
  • 在运行时构造类的实例对象
  • 在运行时获取或修改类/成员的属性
  • 在运行时调用某个类/对象的方法
  • 另外还可获取类的其他信息,比如描述修饰符、父类信息等

针对动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。反射机制在运行时只能调用methods或改变fields内容,却无法修改程序结构或变量类型。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。

原理

用于操作反射相关的主要有以下5个类:

  • java.lang.Class: 代表类
  • java.lang.reflect.Constructor: 代表类的构造方法
  • java.lang.reflect.Field: 代表类的属性
  • java.lang.reflect.Method: 代表类的方法
  • java.lang.reflect.Modifier:代表类、方法、属性的描述修饰符。

Constructor、Field、Method这三个类都继承AccessibleObject,该对象有一个非常重要的方法setAccessible(boolean flag), 用于保证反射可调用非Public的属性与方法。Modifier是指描述修饰符,包含如下范围: public, protected, private, abstract, static, final, transient, volatile, synchronized, native, strictfp, interface。

Constructor

通过java.lang.reflect.Constructor来操作类的构造方法

方法 含义
getConstructors() 获得类的所有public构造方法
getDeclaredConstructors() 获得类的所有构造方法
getConstructor(Class[] parameterTypes) 获得类的特定public构造方法
getDeclaredConstructor(Class[] params) 获取类的特定构造方法

Field

通过java.lang.reflect.Field来获取和修改成员属性,其中getField和getDeclaredField的核心区别就是是否指定类型为public

方法 含义
getFields() 获得类的所有public属性
getDeclaredFields() 获得类的所有属性
getField(String name) 获得类的特定public属性
getDeclaredField(String name) 获取类的特定属性

Method

通过java.lang.reflect.Method来执行成员方法

方法 含义
getMethods() 获得类的所有public成员方法
getDeclaredMethods() 获得类的所有成员方法
getMethod(String name, Class[] parameterTypes) 获得类的特定public成员方法
getDeclaredMethod(String name, Class[] parameterTypes) 获取类的特定成员方法

Class 类的原理

Java 所有的类都是继承于类 Object,其内声明了多个应该被所有 Java 类覆写的方法:hashCode()、equals()、clone()、toString()、notify()、wait()、getClass()等,其中 getClass 返回的便是一个 Class 类的对象。Class 类也同样是继承 Object 类,拥有相应的方法。

Java 程序在运行时,运行时系统对每一个对象都有一项类型标识,用于记录对象所属的类。虚拟机使用运行时类型来选择相应方法去执行,保存所有对象类型信息的类便是 Class 类。Class 类没有公共构造方法,Class 对象是在加载类时由 Java 虚拟机以及通过调用 ClassLoader 的 defineClass 方法自动构造的,因此不能显式地声明一个 Class 对象。

虚拟机为每种类型管理一个独一无二的 Class 对象。也就是说,每个类型都有一个 Class 对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的 Class 对象是否已经加载。如果没有加载,JVM 就会根据类名查找 .class 文件,并将其 Class 对象载入。

基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。 每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。一般某个类的 Class 对象被载入内存,它就用来创建这个类的所有对象。

反射实例

对于正常方式来调用方法,往往只需要一行到两行代码,即可完成相应工作。而反射则显得比较繁琐,之所以繁琐仍会才用反射方式,是因为反射能干很多正常实例化对象的方式所无法做到的事。比如操作那些 private 的类、方法、属性,以及 @hide 标记过的类、方法、属性。

创建对象

  1. //根据类名来获取类
  2. Class clazz = Class.forName("java.lang.String");
  3. //根据对象来获取类
  4. Class clazz = object.getClass();
  5. //根据类来实例化对象
  6. Object obj = clazz.newInstance();
  7. //获取无参的构造函数
  8. Constructor c = clazz.getConstructor(null);
  9. //获取参数为String,int的构造函数
  10. Constructor c = clazz.getConstructor(String.class, int.class);
  11. //用于调用私有构造方法
  12. c.setAccessible(true);
  13. Object obj = c.newInstance("gityuan.com", 2015);

获取/修改属性

  1. 获取对象属性

    1. public static Object getField(Object object, String fieldName) {
    2. Class clazz = object.getClass();
    3. Field field = clazz.getDeclaredField(fieldName);
    4. field.setAccessible(true);
    5. return field.get(object);
    6. }
  2. 修改对象属性

    1. public static boolean setField(Object object, String fieldName, Object fieldValue) {
    2. Class clazz = object.getClass();
    3. Field field = clazz.getDeclaredField(fieldName);
    4. field.setAccessible(true);
    5. return field.set(object, fieldValue);
    6. }
  3. 获取类的静态属性

    1. public static Object getField(Class clazz, String fieldName) {
    2. Field field = clazz.getDeclaredField(fieldName);
    3. field.setAccessible(true);
    4. return field.get(null);
    5. }
  4. 修改类的静态属性

    1. public static boolean setField(Class clazz, String fieldName, Object fieldValue) {
    2. Field field = clazz.getDeclaredField(fieldName);
    3. field.setAccessible(true);
    4. return field.set(null, fieldValue);
    5. }

    调用方法

  5. 调用对象方法

    1. public static Object invokeMethod(Object object, String methodName, Class[] argsType, Object... args) {
    2. Class clazz = object.getClass();
    3. Method method = clazz.getDeclaredMethod(methodName, argsType);
    4. return method.invoke(object, args);
    5. }
  6. 调用类的静态方法

    1. public static Object invokeMethod(Class clazz, String methodName, Class[] argsType, Object... args) {
    2. Method method = clazz.getDeclaredMethod(methodName, argsType);
    3. method.setAccessible(true);
    4. return method.invoke(null, args);
    5. }

    调用内部类

假设 com.reflect.Outer 类,有一个内部类 Inner 和静态内部类 StaticInner。 那么静态内部类的构造函数为Outer$StaticInner(); 而普通内部类的构造函数为 Outer$Inner(Outer outer),多了一个 final 的 Outer 类型属性,即 Outer$Inner.this$0,用于存储外部类的属性值,也就是说非 static 内部类保持了外部类的引用。

直接实例化内部类方法如下:

  1. // 静态内部类
  2. Outer.StaticInner sInner = new Outer.StaticInner();
  3. // 非静态内部类
  4. Outer.Inner inner = new Outer().new Inner();

内部类的类名使用采用$符号,来连接外部类与内部类,格式为outer$Inner

  1. String className = "com.reflect.Outer$Inner";
  2. Class.forName(className);

除了格式了差异,关于内部类的属性和方法操作基本相似,下面以调用该静态类的静态方法为例

  1. public static Object invokeMethod(String methodName, Class[] argsType, Object... args) {
  2. Class clazz = Class.forName(“com.reflect.Outer$StaticInner");
  3. Method method = clazz.getDeclaredMethod(methodName, argsType);
  4. method.setAccessible(true);
  5. return method.invoke(null, args);
  6. }

总结

反射机制为解耦合提供了保障机制,也为在运行时动态修改属性和调用方法提供的可能性。在 Android 的源码中,我们会发现有很多被 @hide 标记的类,它的作用是使这个类或方法在生成 SDK 时不可见。那么应用程序便不可以直接调用。而反射机制可调用 @hide 标记的类或方法,如入无人之地,畅通无阻。不过从 Android P 开始就不允许调用 @hide 方法,会在虚拟机层面拦截直接抛出异常。