Java 反射

Java 反射

反射是什么

我们先来回顾一下之前的深入理解 JVM 虚拟机,还记得方法区吗?JVM 运行时数据区有五大区域:程序计数器虚拟机栈本地方法栈方法区

在方法区中,主要存放:类信息方法代码final静态变量JIT 即时编译器编译后的代码。每个已加载的类中还有一个运行时常量池,用于存储字面量final静态变量符号引用

说白了,方法区其实就是存储 .class 文件的地方,方法区的这些数据是由类加载器(ClassLoader)生成的,与此同时,类加载器还在 Java 堆中创建了一个与之对应的 java.lang.Class 对象,作为该类在方法区的各种数据的访问入口。

因此,我们可以利用这个 Class 对象来访问对应类的元数据,比如:获取它的构造方法、成员方法、成员变量,创建该类的对象、调用指定方法、获取和修改指定字段等等。而这就是所谓的反射机制(Reflection)

因此,我们要使用反射,第一步就是获取这个类的 Class 对象,然后通过这个 Class 对象进行各种反射操作。获取 Class 对象的途径:
1、通过类的隐藏静态属性class,如String.classint.class
2、通过继承自 Object 的getClass()成员方法,如obj.getClass()
3、通过 Class 类的 forName() 静态方法,其中 name 为全限定类名:
Class<?> forName(String name) throws ClassNotFoundException
Class<?> forName(String name, boolean init, ClassLoader loader) throws ClassNotFoundException

有几点要说明的是:
1、对于基本类型包装类,除了有 class 静态属性外,还有一个TYPE静态属性,而这个TYPE属性,其实就是其对应的基本类型的class属性,它们是同一个引用!比如,int.class == Integer.class为 false、int.class == Integer.TYPE为 true。

2、通过ClassName.class方法获取 Class 对象和通过classObj.getClass()方法获取 Class 有什么不同吗?一般情况下没有区别的,前者是通过静态属性,后者是通过成员方法。不过,如果你使用基类指针引用了派生类对象的话,就不一样了,这时候,getClass()始终是获取该对象所属类的 Class 对象,而不是它的基类!

3、类的全限定名是什么?假设 Test.class 位于 com.zfl9 包,那么 Test 这个类的全限定类名就是com.zfl9.Test。然后提一下forName()的第二个重载形式:

  • init参数:如果为 true 则执行该类的<clinit><>类构造器方法,否则只进行加载连接阶段;
  • loader参数:指定用于加载该类的类加载器。如果为 null,则使用 bootstrap 启动类加载器加载(建议指定)。

forName(String name)其实就是forName(name, true, currentLoader),currentLoader 是调用者的类加载器。

反射 API 位于java.lang.reflect,要使用反射,首先应获取类的 Class 对象,然后根据 Class 对象获取其它对象。

JDK1.6 - 反射类图:
JDK1.6 - 反射类图

JDK1.8 - 反射类图:
JDK1.8 - 反射类图

Type 接口及其子接口是与泛型相关的,AnnotatedType 接口及其子接口则是与注解相关的,我们主要关注Constructor构造器、Method方法、Field字段、Parameter参数、以及Class入口类。

这里说一下 AccessibleObject 类,该类为Constructor 构造器Method 方法Field 字段的共同父类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力,即允许我们访问非 public 成员。其主要方法为:
boolean isAccessible():获取访问性 flag,默认为 false,即遵循访问性检查
void setAccessible(boolean flag):设置访问性,true: 忽略访问性检查、false: 遵循访问性检查
static void setAccessible(AccessibleObject[] array, boolean flag):静态方法,批量设置访问性

反射修改 String 对象

反射修改 final 变量

其实这都没什么可惊讶的,所谓的访问性修饰符,只不过是写给编译器看的;而 final 常量也仅仅是语义上的不可修改,事实上,我们只要能够获得它们的地址,就可以任意修改,因此,我们可以说,反射提供了部分 C/C++ 指针的能力

Class 类

java.lang.Class 类(泛型)的相关方法:

Field 类

所谓的字段(Field),就是我们平常所说的成员变量(静态、实例)。

java.lang.reflect.Field 类的相关方法:

例子:

Method 类

java.lang.reflect.Method 类的相关方法:

例子:

Constructor 类

java.lang.reflect.Constructor 类(泛型)的相关方法:

例子:

Array 类

java.lang.reflect.Array 类(工具类,全为静态方法)的相关方法:

例子:

Annotation 注解

注解是什么
注解是 Java 5 的新特性,注解可以看做一种注释或者元数据(MetaData),也可以理解为高级点的注释语法。

注解标识符
注解标识符由一个@符后面跟一个Java 标识符构成,类似于这样:@MyAnnotation

注解的元素
注解可以包含一些元素,这些元素类似于属性或者参数,可以用来设置值,供以后获取。
比如一个包含两个元素的@Entity注解:@Entity(tableName = "vehicles", primaryKey = "id")
上面的注解中有两个元素,tableNameprimaryKey,它们各自都被赋予了自己的元素值。

注解的位置
可用于注释:package包类/接口/枚举注解元注解)、构造方法成员方法方法参数成员变量局部变量等。

在下边这个例子中,注解分别用在了类、字段、方法、参数和局部变量中:

四种元注解
元注解:即注解的注解,你可以和数据、元数据做类比。

java.lang.annotation 包提供了 4 种元注解:
1) @Target,限制注解的作用范围,作用范围由java.lang.annotation.ElementType枚举定义:

  • TYPE:类、接口、枚举
  • FIELD:字段
  • METHOD:方法
  • PARAMETER:方法参数
  • CONSTRUCTOR:构造器
  • LOCAL_VARIABLE:局部变量
  • ANNOTATION_TYPE:注解类型
  • PACKAGE:包
  • TYPE_PARAMETER:类型参数(JDK1.8)
  • TYPE_USE:类型使用声明(JDK1.8)
  • 如果注解没有声明@Target,则默认作用范围为所有 Target

2) @Retention,限制注解的生命周期,生命周期由java.lang.annotation.RetentionPolicy枚举定义:

  • SOURCE:注解仅存于源代码中,在编译过程中有效,不会保存至 .class 字节码文件
  • CLASS默认值,注解在 .class 字节码文件中可用,在类被加载时被 JVM 丢弃
  • RUNTIME:注解在运行时也可用,JVM 不会将注解信息丢弃,可以通过反射获取注解信息

3) @Inherited,表示此注解是可继承的,如果父类被此注解标示,则它的子类也会继承父类的该注解
4) @Documented,表示此注解属于文档的一部分,因此它可以被javadoc及类似的文档工具所记录

内置注解
Java 本身提供了三个内置注解@Override@Deprecated@SuppressWarnings;位于 java.lang 包。

1) @Override,用于注解方法(Method),属于源码(Source)注解,并且它是一个标记注解(没有元素)。
该注解用于在编译期间检查当前方法是否正确的重写了基类中的被重写方法。如果没有则抛出一个编译错误,若已正确重写则被忽略,不会保存到 .class 字节码文件中。

2) @Deprecated,用于注解除注解外的所有 Target,属于运行时(Runtime)注解,并且它是一个标记注解(没有元素),同时还会被包含至 Javadoc 中。
该注解用于表示被注解的 Target 不被 Java 赞成使用,这些 Target 是过时的,废弃的,如果在编码中使用了这些 Target 则抛出一个编译警告。

3) @SuppressWarnings,用于注解除注解、Package 外的所有 Target,属于源码(Source)注解,该注解有一个元素String[] value()
该注解用于抑制被注解的 Target 中出现的 Warnings 编译警告,编译警告类型(即 value 的值)有:

  • all:to suppress all warnings
  • boxing:to suppress warnings relative to boxing/unboxing operations
  • cast:to suppress warnings relative to cast operations
  • dep-ann:to suppress warnings relative to deprecated annotation
  • deprecation:to suppress warnings relative to deprecation
  • fallthrough:to suppress warnings relative to missing breaks in switch statements
  • finally:to suppress warnings relative to finally block that don’t return
  • hiding:to suppress warnings relative to locals that hide variable
  • incomplete-switch:to suppress warnings relative to missing entries in a switch statement (enum case)
  • javadoc:to suppress warnings relative to javadoc warnings
  • nls:to suppress warnings relative to non-nls string literals
  • null:to suppress warnings relative to null analysis
  • rawtypes:to suppress warnings relative to usage of raw types
  • resource:to suppress warnings relative to usage of resources of type Closeable
  • restriction:to suppress warnings relative to usage of discouraged or forbidden references
  • serial:to suppress warnings relative to missing serialVersionUID field for a serializable class
  • static-access:to suppress warnings relative to incorrect static access
  • static-method:to suppress warnings relative to methods that could be declared as static
  • super:to suppress warnings relative to overriding a method without super invocations
  • synthetic-access:to suppress warnings relative to unoptimized access from inner classes
  • sync-override:to suppress warnings because of missing synchronize when overriding a synchronized method
  • unchecked:to suppress warnings relative to unchecked operations
  • unqualified-field-access:to suppress warnings relative to field access unqualified
  • unused:to suppress warnings relative to unused code and dead code

自定义注解
除了可以使用 Java 内置注解外,我们还可以创建自定义注解。定义注解使用关键字@interface,就和定义一个 interface 接口一样简单。Annotation 注解和 interface 接口其实很相似,注解其实就是一种特殊的 interface 接口

例子:

从反编译结果来看,注解类型 MyAnnotation 实际上是 java.lang.annotation.Annotation 的子接口!定义一个注解使用关键字:@interface、定义一个接口使用关键字:interface。那么我们可以大胆推断,注解也可以有成员变量和成员方法,并且和接口具有相同的属性(基本相同)。

首先我们来看一下 java.lang.annotation.Annotation 接口的定义:

我们只需要实现annotationType()方法,其它的几个 Object 类都帮我们提供了默认实现。

1) 尝试模拟注解类型,失败:

2) 尝试实现 Annotation 接口,成功:

3) 注解也可以有方法和属性,成功:

和接口一样,注解中的字段都是public static final修饰的,方法都是public abstract修饰的。

注解中的元素
前面我们创建的自定义注解是一个标记注解,即没有元素的注解。注解也可以有元素,元素可以有多个,元素其实就是接口中的抽象方法

例子:

在给元素赋值的时候需要指明元素的名称,语法和调用一个函数是一样的。

注解元素的类型
所谓的元素类型其实就是抽象方法的返回值类型
1) 八大基本类型(boolean、byte、char、short、int、long、float、double)
2) String 字符串
3) Class 类
4) enum 枚举
5) Annotation 注解接口
6) 以上所有类型的数组

使用其它类型会抛出一个编译错误。

元素默认值
只需要使用 default 关键字就可以为一个元素指定其默认值,如:

提供的默认值不能为 null,只能为一个字面量或者是一个final 常量(其值为一个字面量)。

value 元素
如果注解只有一个元素,并且元素名为value,那么可以省略元素名,直接赋值,如:

因此,如果注解只有一个元素,请务必将它命名为 value