反射,是指一种能在运行时动态加载、分析类的能力。反射被广泛地用于那些需要在运行时检测或修改程序行为的程序中。这是一个相对高级的特性,使用反射技术应当具备相当的Java语言基础。我们可以通过反射机制让应用程序做一些几乎不可能做到的事情。
一. Class类
在java.lang包中有一个特殊的类,即Class类。JVM会为所有被加载的类创建一个对应的Class类的对象,这个对象保存了类的运行时信息。我们可以通过Class对象得到对应的类的一些特征,如类的名字,类有哪些属性,有哪些方法,类的超类是谁,甚至可以直接调用类中的方法。Class类是Java反射技术的基础。
三种获取Class实例的方法:
1. 通过Object.getClass()方法获取。
Object类是所有类的超类,在这个类中有一个getClass()方法,它可以返回这个类的Class对象。我们自定义一个简单的Point类来进行测试:
class Point { }
Point pt = new Point(); Class c1 = pt.getClass(); //通过getClass()方法得到Class对象
2. 通过 类.class 方式获取
如果我们只知道一个类类型,没有此类的一个实例,那么就可以这样:
Class c2 = Point.class;
也就是说,每个类都有一个静态的class成员,可以直接用 类.class的方式得到。
3.通过Class.forName()方式获取
Class的静态方法forName()允许我们通过类的全名来得到Class对象。它需要一个字符串作为参数:
Class c3 = Class.forName("Point");
这时候需要捕获一个 ClassNotFoundException.
二、通过Class对象调用构造函数、成员方法
1. 调用无参数的构造函数创建对象
我们上面定义的Point类并没有定义构造方法,因此编译器会为我们提供一个无参数的默认构造方法。如果想要通过这个无参数的构造方法,则只需要调用Class的newInstance()方法即可得到Point类的一个实例:
Point pt2 = (Point)c1.newInstance(); //newInstance返回Object,因此需要强制转换
2. 调用有参数的构造函数创建对象
现在我们为Point类添加一个有参数的构造方法:
class Point { public Point(int i) { System.out.println(i); } }
如果我们想要调用这个带参数的构造方法,则必须先用 Class对象的getConstructors() 方法来得到一个表示(标识为public)构造方法的对象的数组。在java.lang.reflect包中有一个Constructor类,这个类就代表了一个构造方法。因此 getConstructors() 返回的是Constructor[] 数组。
Constructor[] cons = c1.getConstructors();
因为我们定义的Point类显然只有一个构造函数,所以我们直接使用cons[0]。
在Constructor类中,也有一个newInstance()方法,不过这个方法是带参数的,需要传递一个Object类型的数组。为什么呢?因为我们要调用的构造方法是带参数的,如果不告诉编译器这些参数应该怎么填,那就无法成功地调用该构造方法了。因此,这个Object数组顺理成章地应该就是要传递的参数了。
那么如何才能知道构造方法需要什么类型的参数,到底有几个参数呢?我们可以调用Constructor中的getParameterTypes()来获取所有参数的类型,该方法返回一个Class[]数组,数组中的每一个元素就代表了参数的类型。如本例中,Point的构造方法有一个int 类型的参数,那么返回的Class[]数组就只有一个元素,且这个元素会是一个Integer类的Class对象。当得知参数类型是Integer后,我们就可以为构造方法传递参数了。示例:
Constructor[] cons = c1.getConstructors(); Class[] parmsType = cons[0].getParameterTypes(); //得到参数的类型 Object[] parms = new Object[parmsType.length]; //这个数组用来为newInstance()传参 //遍历parmsType数组,并为Object数组赋值 for(int i = 0 ; i < parms.length ; ++i) { if(parmsType[i].isPrimitive()) //判断是否是基本数据类型 { parms[i] = new Integer(100); } }
此时就可以创建Point对象了:
Point pt = (Point)cons[0].newInstance(parms);
此时,我们看到控制台打印出100,说明Point类构造成功。
3.调用Class所对应类的成员方法
先通过Class的getDeclaredMethods()方法来得到所有被声明的方法,即返回一个Method[]类型的数组。然后可以调用Method对象的invoke()方法实现方法的调用。invoke()依然分有参和无参两种情况。此时的调用方式与上面调用newInstance()的过程类似,不再赘述。
三、反射的缺点:(从官方指南上找的,个人翻译水平差,大家凑合看。。。。意会就行。。)
Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.
尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心:
性能第一
Performance Overhead
Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。
安全限制
Security 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 operations 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 abstractions and therefore may change behavior with upgrades of the platform.
由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。