简介
反射能够让运行在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对象,可以通过其中的方法来获取到该类中的构造方法、域和方法。对应的方法分别是getConstructor
、getField
和getMethod
,只会获取所有public修饰的元素。这三个方法还有对应的getDeclaredXXX
版本,区别在于getDeclaredXXX
版本的方法只会获取该类自身所声明的元素(包括所有,不论public、protected和private还是默认的修饰符),而不会考虑继承下来的。
操作Java对象
对Java对象的操作包括:
- 动态创建一个Java类的对象
- 获取某个域的值
- 调用某个方法12345678910111213141516171819202122public 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();}}
运行结果如下:
反射对泛型的支持
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来使用。
域类型
定义一个泛型域
比如在代码中声明了一个域是List
方法返回类型
定义方法返回类型为泛型的方法
使用反射获取方法返回类型的类型参数
方法参数类型
定义方法参数为泛型的方法
使用反射获取方法的泛型参数
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
,TypeVariable
和WildcardType
。
Class | 通常的Class类型 |
---|---|
ParameterizedType | 参数化类型,List |
TypeVariable | 各种类型变量的通用接口,T |
GenericArrayType | 一种元素类型是参数化类型或者类型变量的数组类型,T[] |
WildcardType | 一种通配符类型表达式,如? ,? extends Number , ? super Integer |
反射对注解的支持
同样可以通过反射来获取注解信息。
感谢:
http://blog.jrwang.me/2015/java-reflection/
http://www.infoq.com/cn/articles/cf-java-reflection-dynamic-proxy