Java 反射

简介

反射能够让运行在JVM中的程序检测和修改运行时的行为,使用反射API可以在运行时获取对象的内部结构信息。

JVM的类加载机制允许从多种源动态地加载Java类,但是动态类加载有一个重要的缺点,即我们在使用动态加载的类对象时,往往对这个类知之甚少,甚至于一无所知。这种情况下就需要使用一种动态编程技术——反射。

优点:
可以在运行时加载类,并获取类的内部结构,还可以创建新的对象和调用对象中的方法等,增加了运行时刻的灵活性。

缺点:
使用反射的一个最大的缺点就是性能稍差,需要在灵活性和性能之间进行权衡。

Class类

反射使用Class对象提供的基本元数据,能够从Class对象中获取方法和字段的名称,并获取表示方法和对象的字段。基于此,我们就能在之前类型未知的对象上获取甚至调用它的任意方法。

Java中存在一个Class类,Class对象是在运行中的Java进程里表示实时类型的方式。Class对象包含了指定类型的元数据,包括这个类中定义的方法、字段、构造方法等。
在JVM中,每加载一个类,就会在堆内存中为这个类生成一个java.lang.Class 对象。Class类并没有提供public的构造方法,在类加载完成后,JVM会自动为其生成相应的Class对象。要使用反射,首先就必须获取到Class对象

获取Class对象

getClass()方式

通过一个类的实例来得到Class对象
Class<?> cls = obj.getClass();

.class方式

如果在编译期知道类名,可以使用以下方式得到Class对象:
Class<?> cls = String.class;或者Class<?> cls = Integer.TYPE;

Class.forName()方式

如果在编译期不知道类名,但是在运行期可以获得,可以使用以下方式得到Class对象:
Class<?> cls = Class.forName("类的全限定名");

Class.forName()是调用当前所在类的类加载器来加载的。

这种方式其实是利用反射API把指定字符串的类加载到内存中,所以也叫类加载器加载方法。
这样的话,它会把该类的静态方法、静态属性和静态代码全部加载到内存中。但这时候,对象还没有产生。所以为什么静态方法不能访问非静态属性和方法。因为静态方法和属性产生的时机在非静态属性和方法之前。

当接着运行cls.newInstance();时,会进行类的实例化操作(执行非静态的属性初始化和非静态代码块,然后调用构造方法完成实例化)。

反射API

Java 反射API的主要作用体现在两个方面:在运行时刻获取类的内部结构和操作一个Java对象。

获取类的内部结构

通过Class对象,可以通过其中的方法来获取到该类中的构造方法、域和方法。对应的方法分别是getConstructorgetFieldgetMethod,只会获取所有public修饰的元素。这三个方法还有对应的getDeclaredXXX版本,区别在于getDeclaredXXX版本的方法只会获取该类自身所声明的元素(包括所有,不论public、protected和private还是默认的修饰符),而不会考虑继承下来的。

操作Java对象

对Java对象的操作包括:

  1. 动态创建一个Java类的对象
  2. 获取某个域的值
  3. 调用某个方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public static void testReflectAPI(){
    Class<?> cls = MyClass.class;
    try {
    // 获取构造方法
    Constructor constructor = cls.getConstructor(String.class, int.class);
    // 创建对象
    MyClass obj = (MyClass) constructor.newInstance("Roger", 23);
    // 获取方法
    Method method = cls.getMethod("print", String.class);
    // 调用方法
    method.invoke(obj, "method invoke");
    // 获取私有域
    Field field = cls.getDeclaredField("name");
    field.setAccessible(true);
    System.out.println("private field reflect: " + field.get(obj));
    // 获取共有域
    Field field1 = cls.getField("age");
    System.out.println("public field reflect: " + field1.getInt(obj));
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

运行结果如下:

1
2
3
MyClass.print:method invoke
private field reflect: Roger
public field reflect: 23

反射对泛型的支持

Java 5中引入了泛型的概念之后,Java反射API也做了相应的修改,以提供对泛型的支持。由于类型擦除机制的存在,泛型类中的类型参数等信息,在运行时刻是不存在的。JVM看到的都是原始类型。对此,Java 5对Java类文件的格式做了修订,添加了Signature属性,用来包含不在JVM类型系统中的类型信息。比如以java.util.List接口为例,在其类文件中的Signature属性的声明是<E:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/Collection<TE;>;; ,这就说明List接口有一个类型参数E。在运行时刻,JVM会读取Signature属性的内容并提供给反射API来使用。

域类型

定义一个泛型域

1
List<String> list;

比如在代码中声明了一个域是List类型的,虽然在运行时刻其类型会变成原始类型List,但是仍然可以通过反射来获取到所用的实际的类型参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 反射获取域的泛型类型
Field field = cls.getDeclaredField("list"); // list的类型是List<String>
Type type = field.getGenericType();
if (type instanceof ParameterizedType){
ParameterizedType paramType = (ParameterizedType) type;
Type[] actualTypes = paramType.getActualTypeArguments();
for (Type aType: actualTypes){
if (aType instanceof Class){
Class aCls = (Class) aType;
System.out.println("field: " + aCls.getName()); // 输出java.lang.String
}
}
}

方法返回类型

定义方法返回类型为泛型的方法

1
2
3
public List<String> getList(){
return null;
}

使用反射获取方法返回类型的类型参数

1
2
3
4
5
6
7
8
9
10
Method method1 = cls.getMethod("getList");
Type returnType = method1.getGenericReturnType();
if (returnType instanceof ParameterizedType){
ParameterizedType paramType = (ParameterizedType) returnType;
Type[] typeArg = paramType.getActualTypeArguments();
for (Type aType: typeArg){
Class aCls = (Class) aType;
System.out.println("return: " + aCls.getName()); // 输出java.lang.String
}
}

方法参数类型

定义方法参数为泛型的方法

1
2
public void setList(List<String> list){
}

使用反射获取方法的泛型参数

1
2
3
4
5
6
7
8
9
10
11
12
Method method2 = cls.getMethod("setList", List.class);
Type[] genericParameterTypes = method2.getGenericParameterTypes();
for (Type genericParameterType: genericParameterTypes){
if (genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] paramArgTypes = aType.getActualTypeArguments();
for (Type paramArgType: paramArgTypes){
Class paramArgClass = (Class) paramArgType;
System.out.println("arg: " + paramArgClass.getName()); // 输出java.lang.String
}
}
}

Type接口

Java里Type接口是所有type的超接口,Class类是Type接口的一个实现。

Type is the common superinterface for all types in the Java programming language. These include raw types, parameterized types, array types, type variables and primitive types.

为了通过反射获取泛型信息,Java分别新增了几个Type接口的子接口,除了上面提到的ParameterizedType外,还有GenericArrayType,TypeVariableWildcardType

Class 通常的Class类型
ParameterizedType 参数化类型,List
TypeVariable 各种类型变量的通用接口,T
GenericArrayType 一种元素类型是参数化类型或者类型变量的数组类型,T[]
WildcardType 一种通配符类型表达式,如?? extends Number? super Integer

反射对注解的支持

同样可以通过反射来获取注解信息。

1
2
3
4
5
6
7
8
9
public void findDeprecatedMethods(Class<?> clz) {
for (Method m : clz.getMethods()) {
for (Annotation a : m.getAnnotations()) {
if (a.annotationType() == Deprecated.class) {
System.out.println(m.getName());
}
}
}
}


感谢:
http://blog.jrwang.me/2015/java-reflection/
http://www.infoq.com/cn/articles/cf-java-reflection-dynamic-proxy