详解 Java 反射机制及其在 Android 中的应用

什么是 Java 的反射机制

Java 反射是 Java 被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时通过 reflection APIs 取得任何一个已知名称的 class 的内部信息,包括其 modifiers(诸如 public,static等)、superclass(例如 Object)、实现的 interfaces(例如 Cloneable),也包括 fields 和 methods 的所有信息,并可于运行时改变 fields 内容或唤起 methods。

Java 反射机制容许程序在运行时加载、探知、使用编译期间完全未知的 classes。换言之,Java 可以加载一个运行时才得知名称的 class,获得其完整结构。

JDK 中提供的 Reflection API

Java 反射相关的 API 在包 java.lang.reflect 中,JDK 1.8 的 reflect 包如下图:http://7xsosy.com1.z0.glb.clouddn.com/Screen%20Shot%202016-08-09%20at%2020.55.02.png

  • Member 接口:该接口可以获取有关类成员(域或者方法)构造函数的信息。
  • AccessibleObject 类:该类是域对象(field)、方法对象(method)、构造函数对象(constructor)的基础类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。
  • Array 类:该类提供动态地生成和访问 Java 数组的方法。
  • Constructor 类:提供一个类的构造函数的信息以及访问类的构造函数的接口。
  • Field 类:提供一个类的域的信息以及访问类的域的接口。
  • Method 类:提供一个类的方法的信息以及访问类的方法的接口。
  • Modifier 类:提供了 static 方法和常亮,对类和成员访问修饰符进行解码。
  • Proxy 类:提供动态生成代理类和类实例的静态方法。

Java 反射机制提供了什么功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任一个对象的方法
  • 在运行时创建新类对象

在使用 Java 的反射功能时,基本首先都眼获取类的 Class 对象,再通过 Class 对象获取其他的对象

这里首先定义用于测试的类:

Class Type {
    public int pubIntFiels;
    public String pubStringField;
    private int prvIntField;

    public Type() {
        Log("Default Constructor");
    }

    Type(int arg1, String arg2) {
        pubIntField = arg1;
        pubStringField = arg2;

        Log("Constructor with parameters");
    }

    public void setIntField(int val) {
        this.prvIntField = val;
    }

    public int getIntField() {
        return prvIntField;
    }

    private void Log(String msg) {
        System.out.println("Type:"+ msg);
    }
}

Class ExtendType extends Type {
    public int pubIntExtendField;
    public String pubStringExtendField;
    private int prvIntExtendField;

    public ExtendType() {
        Log("Default Constructor");
    }

    ExtendType(int arg1, String arg2) {
        pubIntExtendField = arg1;
        pubStringExtendField = arg2:

        Log("Constructor with parameters");
    }

    public void setIntExtendField(int field7) {
        this.prvIntExtendField = field7;
    }

    public int getIntExtendField() {
        return prvIntExtendField;
    }

    private void Log(String msg) {
        System.out.println("ExtendType:"+ msg);
    }
}

1.获取类的 Class 对象

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。获取类的 Class 对象有多种方式:

  • 调用 getClass:

Boolean var1 = true;
Class<?> classType2 = var1.getClass();
System.out.println(classType2);

输出:class java.lang.Boolean

  • 运用 .class 语法:

Class<?> classType4 = Boolean.class;
System.out.println(classType4);

输出:class java.lang.Boolean

  • 运用静态方法 Class.forName()

Class<?> classType5 = Class.forName("java.lang.Boolean");
System.out.println(classType5);

输出:class java.lang.Boolean

  • 运用 primitive wrapper classes 的 TYPE 语法:

    这里返回的是原生类型,和 Boolean.class 返回的不同


Class<?> classType3 = Boolean.TYPE;
System.out.println(classType3);

输出:boolean

2.获取类的 Fields

可以通过反射机制得到某个类的某个属性,然后改变对应于这个类的某个实例的该属性值

Class\ 类提供了几个方法获取类的属性:

  • public Field getField(String name):返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段
  • public Field[] getFields():返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段
  • public Field getDeclaredField(String name):返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段
  • public Field[] getDeclaredFields():返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有的字段

==getFields== 和 ==getDeclaredFields== 的区别:

getFileds 返回的是声明为 public 的属性,包括父类 中定义的;

getDeclardFields 返回的是指定类定义的 所有定义 的属性,不包括父类 的。

3.获取类的 Method

通过反射机制得到某个类的某个方法,然后调用对应于这个类的某个实例的该方法

Class\ 类提供了一个方法获取类的方法:

  • public Method getMethod(String name, Class<?>...parameterTypes):返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法
  • public Method[] getMethods():返回一个包含某些 Method 对象的数组。这些对象反映此 Class 对象所表示的类或接口,包括那些由该类或接口声明的以及从超类和超接口继承的那些类或接口的公共成员方法
  • public Method getDeclaredMethod(String name, Class<?> parameterTypes):返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法
  • public Method[] getDeclaredMethods():返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法

4.获取类的 Constructor

通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例

Class\ 类提供了几个方法获取类的构造器:

  • public Constructor\<T> getConstructor(Class<?>... parameterTypes):返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法
  • public Constructor<?>[] getConstructors():返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法
  • public Constructor\<T> getDeclaredConstructor(Class<?>... parameterTypes):返回一个 Contructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法
  • public Constructor<?>[] getDeclraedConstructors():返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法,包括公共、保护、默认(包)访问和私有构造方法

5.新建类的实例

通过反射机制创建类的实例,有一下几种方法可以创建:

  • 调用类的 Class 对象的 newInstance 方法,该方法会调用对象的默认构造器,如果没有默认构造器,会调用失败。
  • 调用默认 Constructor 对象的 newInstance 方法。
  • 调用带参数 Constructor 对象的 newInstance 方法。

6.调用类的函数

通过反射获取类 Method 对象,调用 FieldInvoke 方法调用函数

7.设置/获取类的属性值

通过反射获取类的 Field 对象,调用 Field 方法设置或获取值

Java 中的三种类加载器

  1. Bootstrap ClassLoader:此加载器采用 C++ 编写,一般开发中很少见。
  2. Extension ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类。
  3. AppClassLoader加载 classpath 指定的类,是最常用的加载器,同时也是 java 中默认的加载器

类的生命周期

在一个类编译完成之后,下一步就需要开始使用类,如果要使用一个雷,肯定离不开 JVM。在程序执行中 JVM 通过==装载、链接、初始化==这 3 个步骤完成。

类的装载是通过类加载器完成的,加载器将 .class 文件的二进制文件装入 JVM 的方法区,并且在==堆区==创建描述这个类的 java.lang.Class 对象,用来封装数据。但是同一个类只会被类装载器装载一次。链接就是把二进制数据组装为可以运行的状态。

链接分为校验、准备、解析这 3 个阶段

校验一般用来确认此二进制文件是否适合当前的 JVM 版本

准备就是为静态成员分配内存空间,并设置默认值

解析指的是转换常量池中的代码作为直接引用的过程,直到所有的符号引用都可以被运行程序使用,即建立完整的对应关系。

完成之后,类就完成了初始化,初始化之后类的对象就可以正常使用了,直到一个对象不再使用之后,将被垃圾回收期回收,释放空间。当没有任何引用指向 Class 对象时就会被卸载,结束类的生命周期

反射的缺点 Drawbacks of Reflection

性能第一 Performance overhead

Because reflection involves types that are dynamically resolved, certain JVM optimizations can not be performed. Consequently, reflective operations have slower performace than thire non-reflective counterparts, and should be avoided in sections of code which are called frequently in performanc-sensitive applicatioins.

反射包括了一些动态类型,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常需要被调用的代码和对性能要求很高的程序中使用反射。

安全限制 Security of Restrictions

Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.

使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet, 那么这就是个问题了。

内部暴露 Exposure of Internals

Since reflection allows code to perform operarions that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstrctions and therefore may change behaior with upgrades of the platform.

由于反射允许代码执行一些在正常情况下不被允许的操作,例如访问私有属性和方法,所有使用反射可能会导致意料之外的副作用–代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随之变化。

反射在 Android 中的应用

Android FrameWork 中的反射

Activity 的启动过程中 Activity 的对象的创建就用到了反射技术。

ClassLoader 和 DexClassLoader

Java 的动态加载的机制就是通过 ClassLoader 来实现的,ClassLoader 也是实现反射的基石。ClassLoader是 Java 提供的一个类,就是用来加载 Class 文件到 JVM,以供程序使用。

ClassLoader加载文件到 JVM, Android 是基于 DVM.