反射能够让运行在JVM中的程序检测和修改运行时的行为。在Java中使用反射API可以在运行时获取对象的内部结构信息,就仿佛相应的Java类的内部结构(字段、方法、构造方法等)在镜子前清晰地“反射”出来一样。
我们知道,JVM的类加载机制允许从多种源动态地加载Java类,但是动态类加载有一个重要的缺点,即我们在使用动态加载的类对象时,往往对这个类知之甚少,甚至于一无所知。这种情况下就需要使用一种动态编程技术——反射。
在JavaScript等动态语言中,可以很方便地获取、修改一个对象内部的结构,即便事先对这个对象一无所知。如:
1
2
3
4
5
6
7
8
9
|
//JavaScript
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]);
}
}
foo = 2;
eval("foo = foo + 2; alert(foo);");
|
然而,在Java中,如果编译期间不知道一个类的内部信息,要怎么获取呢?反射机制到底要怎么实现呢?这一切都要从Class类说起。简单地说,反射使用Class对象提供的基本元数据,能够从Class对象中获取方法和字段的名称,并获取表示方法和对象的字段。基于此,我们就能在之前类型未知的对象上获取甚至调用它的任意方法。
从Class类说起
Java中存在一个Class类,Class对象是在运行中的Java进程里表示实时类型的方式。Class对象包含了指定类型的元数据,包括这个类中定义的方法、字段、构造方法等。在JVM中,每加载一个类,就会在堆内存中为这个类生成一个java.lang.Class
对象。Class类并没有提供public的构造方法,在类加载完成后,JVM会自动为其生成相应的Class对象。要使用反射,首先就必须获取到Class对象。Class对象有多种获取方式:
getClass()方法
1
2
3
|
Class<?> cls = obj.getClass();
//还可以获取super class
Class<?> superCls = obj.getClass();
|
.class方式
1
2
3
|
Class<?> c1 = int.getClass();
Class<?> c2 = String.getClass();
Class<?> c2 = int[].getClass();
|
基本类型包装类的TYPE字段
1
2
3
4
5
6
7
8
9
10
|
//仅针对基本类型,包括void
Class<?> c1 = Boolean.TYPE;
Class<?> c2 = Byte.TYPE;
Class<?> c3 = Character.TYPE;
Class<?> c4 = Short.TYPE;
Class<?> c5 = Integer.TYPE;
Class<?> c6 = Long.TYPE;
Class<?> c7 = Float.TYPE;
Class<?> c8 = Double.TYPE;
Class<?> c9 = Void.TYPE;
|
Class.forName()
Class类的静态方法,按照路径显式加载相应的类。
1
2
|
Class<?> c1 = Class.forName("java.lang.String");
Class<?> c2 = Class.forName("java.util.LinkedList$Entry");
|
在获取了一个类的Class对象后,就可以获取这个类的结构信息了,基本上都是通过调用Class对象的成员方法的方式。详细可参考JDK文档。
对于一个类来说,最重要的信息莫过于构造函数、成员变量及成员方法了,这三个重要的信息都可以通过Class对象获取,与之相关的三个类分别为java.lang.reflect.Constructor<T>
、java.lang.reflect.Method
、java.lang.reflect.Field
。与之相对应的一些方法为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<?>[] getConstructors()
Constructor<T> getDeclaredConstructor(Class<?>... params)
Constructor<?>[] getDeclaredConstructors()
Field getField(String name)
Field[] getFields()
Field getDeclaredField(String name)
Field[] getDeclaredFields()
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getMethods()
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods() |
其中getDeclaredXXX()
和getXXX()
的区别在于:
Class API |
List of members? |
Inherited members? |
Private members? |
getDeclaredField() |
no |
no |
yes |
getField() |
no |
yes |
no |
getDeclaredFields() |
yes |
no |
yes |
getFields() |
yes |
yes |
no |
使用反射生成对象
通过反射来生成对象的方法有两种:
1. 使用Class对象的newInstance()
方法来创建该Class对象对应类的实例(使用默认的无参构造方法)。
2. 先获取指定的Constructor
(构造方法)对象,再调用该构造方法的newInstance()
方法来生成相应的实例(可使用指定参数的构造方法)。
直接使用Class对象的newInstance()
方法来创建实例很常见,在一些Java框架中都可以见到,如根据配置文件生成相应的对象池:
1
2
3
4
5
|
class Pool {
private static Object getInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return Class.forName(className).newInstance();
}
}
|
第二种方式在创建对象的时候则要先获取Constructor
对象:
1
2
3
4
5
6
7
|
try {
Constructor constructor = MyClass.class.getConstructor(int.class); //获取构造方法
MyClass myClassReflect = constructor.newInstance(10); //创建对象
//...
} catch (Exception e) {
e.printStackTrace();
}
|
鉴于数组的特殊性,反射API中的java.lang.reflect.Array提供了一系列特殊的方法来创建和操作数组:
1
2
3
4
|
Object array = Array.newInstance(String.class, 10); //等价于 new String[10]
Array.set(array, 0, "Hello"); //等价于array[0] = "Hello"
Array.set(array, 1, "World"); //等价于array[1] = "World"
System.out.println(Array.get(array, 0)); //等价于array[0]
|
使用反射调用方法、访问成员变量
使用反射来调用方法需要先获取Method对象,随后调用invoke(Object receiver, Object... args)
即可,其中第一个参数指名调用的对象。注意调用结束的返回值类型是Object。如果是静态方法,第一个参数可以使null。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Object receiver = "a string";
try {
Class<?>[] argTypes = new Class[] {}; //方法参数类型列表,为空则列表为空即可
Object[] args = null; //实际调用时传入的参数,无参则为null
Method meth = receiver.getClass().getMethod("hashCode", argTypes);
Object ret = meth.invoke(receiver, args);
System.out.println(ret);
} catch(IllegalArgumentException | NosuchMethodException | SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException | InvocationTargetException x) {
x.printStackTrace();
}
|
对于非public的方法的引用,还要使用setAccessible(true)
来覆盖Java的访问控制子系统,然后才能执行非公开方法。
1
2
3
4
5
6
7
|
try{
Method method = clz.getDeclaredMethod("privateMethod", argTypes);
method.setAccessible(true); //非public方法必须修改后才能调用
method.invoke(receiver, args);
}catch (Exception e) {
}
|
使用反射访问和修改成员变量则通过 Field 对象进行,基本和 Method 一致,获取和修改分别通过 get() 和 set() 方法,这里不再赘述。
使用反射处理泛型
Java的反射API提供了对泛型的支持。我们通常以为由于泛型擦除机制,泛型类中的类型参数等信息,在运行时刻是不存在的,JVM只能看到原始类型。但实际上,在引入泛型后,类文件的格式做了修订,通过反射依然能获取到泛型信息。下面通过几个简单的例子来说明如何使用反射来获取泛型信息。
泛型变量类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class MyClass {
public List<String> stringList = ...;
}
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments(); //获取实际的类型参数
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}
//output: typeArgClass = java.lang.String
|
泛型方法返回类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class MyClass {
protected List<String> stringList = ...;
public List<String> getStringList(){
return this.stringList;
}
}
Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
}
}
//output: typeArgClass = java.lang.String
|
泛型方法参数类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class MyClass {
protected List<String> stringList = ...;
public void setStringList(List<String> list){
this.stringList = list;
}
}
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
//output: typeArgClass = java.lang.String
|
Type接口
在Java中,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
,TypeVariable
和WildcardType
。
Class |
通常的Class类型 |
ParameterizedType |
参数化类型,List |
TypeVariable |
各种类型变量的通用接口,T |
GenericArrayType |
一种元素类型是参数化类型或者类型变量的数组类型, T[] |
WildcardType |
一种通配符类型表达式, 如?, ? extends Number, ? super Integer |
一种常见的处理各种类型的逻辑如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
if (type instanceof Class) {
Class<?> c = (Class<?>) type;
} else if (type instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) type;
//p.getOwnerType()
//p.getRawType()
//p.getActualTypeArguments()
} else if (type instanceof GenericArrayType) {
GenericArrayType g = (GenericArrayType) type;
//g.getGenericComponentType()
} else if (type instanceof WildcardType) {
WildcardType w = (WildcardType) type;
//w.getUpperBounds()
//w.getLowerBounds()
} else {
// This type is unsupported!
}
|
泛型与异常
Method对象还有一个方法是 getGenericExceptionTypes()。初看这个方法感觉比较奇怪,我们知道由于类型擦除机制,Java中是不允许对异常类使用泛型的。
1
2
|
GenericException<T> extends Throwable{ /// NOT ALLOWED
}
|
那么,getGenericExceptionTypes()方法的用意何在呢?在JDK中的说明是这样的:
>If an exception type is a type variable or a parameterized type, it is created. Otherwise, it is resolved.
如果异常本就是一个类型变量就可以说通了,举个例子:
1
2
3
4
5
6
7
8
9
10
11
|
public class Example {
public static <T extends Throwable> void method() throws T {}
public static void main(String[] args) throws Exception {
Method m = Example.class.getMethod("method");
System.out.println(m.getGenericExceptionTypes()[0]);
System.out.println(m.getExceptionTypes()[0]);
}
}
//output:
//T
//class java.lang.Throwable
|
使用反射获取注解信息
Java同样可以通过反射来获取注解信息,下面给一个简单的例子。找出一个类中被标记为弃用的方法(使用 @Deprecated 标记):
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());
}
}
}
}
|
小结
Java的反射机制十分强大,提供了一种在运行时动态获取对象元数据的手段,并且可以动态地调用对象的方法。即便编译前不知道类的详细信息,只要知道了类的名称,就可以使用反射机制动态地加载类,创建类的实例对象,并动态地调用方法。在很多的Java框架中,反射机制都有着大量的应用。因为类的名称可以通过配置文件等方式获取,反射也可以降低程序的耦合性。当然,使用Java的反射机制也会带来一些问题,如性能问题,安全性问题等,也会导致程序内部逻辑模糊,增加系统的复杂性。
-EOF-
参考
Trail: The Reflection API
Java Reflection - Generics
Java 反射
Java反射机制的学习