java反射机制

来源:互联网 发布:网络视频广告特点 编辑:程序博客网 时间:2024/06/07 16:34
Java  反射机制
反射机制:所谓的反射机制就是 java 语言在运行时拥有一项自观的能力。通过这种能力可以
彻底的了解自身的情况为下一步的动作做准备。下面具体介绍一下 java 的反射机制。这里
你将颠覆原来对 java的理解。
Java的反射机制的实现要借助于 4 个类:class,Constructor,Field,Method;其中 class 代
表的时类对 象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法
对象。通过这四个对象我们可以粗略的看到一个类的各个组 成部分。
Class:程序运行时,java 运行时系统会对所有的对象进行运行时类型的处理。这项信息记
录了每个对象所属的类,虚拟机通常使用运行时类型信息选择正 确的方法来执行(摘自:
白皮书) 。但是这些信息我们怎么得到啊,就要借助于 class 类对象了啊。在 Object 类中定
义了 getClass()方法。我 们可以通过这个方法获得指定对象的类对象。然后我们通过分析这
个对象就可以得到我们要的信息了。
比如:ArrayList arrayList;
Class clazz = arrayList.getClass();
然后我来处理这个对象 clazz。
当然了 Class 类具有很多的方法,这里重点将和 Constructor,Field,Method 类有关系的方
法。
Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查 ,
或者说“自审”,并能直接操作程序的内部属性。Java 的这一能力在实际应用中也许用得不
是很多,但是个人认为要想对 java有个更加深入的了解还是应该掌握的。
1 1 1 1 . 检测类:
reflection 的工作机制
考虑下面这个简单的例子,让我们看看 reflection 是如何工作的。
import java.lang.reflect.*;
public class DumpMethods {
public static void main(String args[]) {
try {
Class c= Class.forName(args[0]);
Method m[] = c.getDeclaredMethods();
for (int i = 0; i < m.length; i++)
System.out.println(m[i].toString());
} catch (Throwable e) {
System.err.println(e);
}
}
}
按如下语句执行:
java DumpMethods java.util.ArrayList
这个程序使用 Class.forName 载入指定的类,然后调用 getDeclaredMethods 来获取这个类
中定义了的方法列表。java.lang.reflect.Methods 是用来描述某个类中单个方法的一个类。
Java类反射中的主要方法
对于以下三类组件中的任何一类来说 -- 构造函数、字段和方法 -- java.lang.Class 提供四种
独立的反射调用,以不同的方式来获得信息。调用都遵循一种标准格式。以下是用于查找构
造函数的一组反射调用:
Constructor getConstructor(Class[] params)-- 获得使用特殊的参数类型的公共构造函数,
Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(与
接入级别无关 )
Constructor[] getDeclaredConstructors()-- 获得类的所有构造函数(与接入级别无关 )
获得字段信息的 Class 反射调用不同于那些用于接入构造函数的调用, 在参数类型数组中使
用了字段名:
Field getField(String name) -- 获得命名的公共字段
Field[] getFields()-- 获得类的所有公共字段
Field getDeclaredField(String name) -- 获得类声明的命名的字段
Field[] getDeclaredFields() -- 获得类声明的所有字段
用于获得方法信息函数:
Method getMethod(String name, Class[] params) -- 使用特定的参数类型, 获得命名的公共方法
Method[] getMethods() -- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params)-- 使用特写的参数类型,获得类声明
的命名的方法
Method[] getDeclaredMethods() -- 获得类声明的所有方法
使用 Reflection:
用于 reflection 的类,如 Method,可以在 java.lang.relfect 包中找到。使用这些类的时候必
须要遵循三个步骤:第一步是获得你想操作的类的 java.lang.Class 对象。在运行中的 Java
程序中,用 java.lang.Class 类来描述类和接口等。
下面就是获得一个 Class 对象的方法之一:
Class c= Class.forName("java.lang.String");
这条语句得到一个 String 类的类对象。还有另一种方法,如下面的语句:
Class c= int.class;
或者
Class c= Integer.TYPE;
它们可获得基本类型的类信息。其中后一种方法中访问的是基本类型的封装类 (如 Intege )
中预先定义好的 TYPE 字段。
第二步是调用诸如 getDeclaredMethods 的方法,以取得该类中定义的所有方法的列表。
一旦取得这个信息,就可以进行第三步了——使用 reflection API 来操作这些信息,如下面
这段代码:
Class c= Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
System.out.println(m[0].toString());
它将以文本方式打印出 String 中定义的第一个方法的原型。
处理对象:
a.创建一个 Class 对象
b.通过 getField 创建一个 Field 对象
c.调用 Field.getXXX(Object)方法(XXX 是 Int,Float 等, 如果是对象就省略; Object 是指实例).
例如:
import java.lang.reflect.*;
import java.awt.*;
classSampleGet {
public static void main(String[] args){
Rectangle r = new Rectangle(100, 325);
printHeight(r);
}
static void printHeight(Rectangle r){
Field heightField;
Integer heightValue;
Class c= r.getClass();
try {
heightField = c.getField("height");
heightValue = (Integer)heightField.get(r);
System.out.println("Height: " + heightValue.toString());
} catch (NoSuchFieldException e) {
System.out.println(e);
} catch (SecurityException e) {
System.out.println(e);
} catch (IllegalAccessException e) {
System.out.println(e);
}
}
}
安全性和反射:
在处理反射时安全性是一个较复杂的问题。反射经常由框架型代码使用,由于这一点,我们
可能希望框架能够全面接入代码,无需考虑常规的接入限制。但是,在其它情况下,不受控
制的接入会带来严重的安全性风险,例如当代码在不值得信任的代码共享的环境中运行时。
由于这些互相矛盾的需求, Java编程语言定义一种多级别方法来处理反射的安全性。基本模
式是对反射实施与应用于源代码接入相同的限制:
从任意位置到类公共组件的接入
类自身外部无任何到私有组件的接入
受保护和打包(缺省接入)组件的有限接入
不过至少有些时候,围绕这些限制还有一种简单的方法。我们可以在我们所写的类中,扩展
一个普通的基本类 java.lang.reflect.AccessibleObject 类。这个类定义了一种 setAccessible 方
法,使我们能够启动或关闭对这些类中其中一个类的实例的接入检测。唯一的问题在于如果
使用了安全性管理 器,它将检测正在关闭接入检测的代码是否许可了这样做。如果未许可 ,
安全性管理器抛出一个例外。
下面是一段程序,在 TwoString 类的一个实例上使用反射来显示安全性正在运行:
public class ReflectSecurity {
public static void main(String[] args){
try {
TwoString ts = new TwoString("a", "b");
Field field = clas.getDeclaredField("m_s1");
// field.setAccessible(true);
System.out.println("Retrieved value is " +
field.get(inst));
} catch (Exception ex) {
ex.printStackTrace(System.out);
}
}
}
如果我们编译这一程序时,不使用任何特定参数直接从命令行运行,它将在 field .get(inst)
调用中抛出一个 IllegalAccessException 异常。如果我们不注释 field.setAccessible(true)代码
行,那么重新编译并重新运行该代码,它将编译成功。最后,如果我们在命令行添加了 JVM
参数 -Djava.security.manager 以实现安全性管理器,它仍然将不能通过编译,除非我们定义
了 ReflectSecurity 类的许可权 限。
反射性能: (转录别人的啊)
反射是一种强大的工具,但也存在一些不足。一个主要的缺点是对性能有影响。使用反射基
本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们的要求。这类
操作总是慢于只直接执行相同的操作。
下面的程序是字段接入性能测试的一个例子,包括基本的测试方法。每种方法测试字段接入
的一种形式 -- accessSame 与同一对象的成员字段协作, accessOther 使用可直接接入的另一
对象的字段,accessReflection 使用可通过反射接入的另一对象的字段。在每种情况下,方
法执行相同的计算 -- 循环中简单的加/乘顺序。
程序如下:
public int accessSame(int loops){
m_value = 0;
for (int index = 0; index < loops; index++) {
m_value = (m_value + ADDITIVE_VALUE) *
MULTIPLIER_VALUE;
}
return m_value;
}
public int acces
sReference(int loops){
TimingClass timing = new TimingClass();
for (int index = 0; index < loops; index++) {
timing.m_value = (timing.m_value + ADDITIVE_VALUE) *
MULTIPLIER_VALUE;
}
return timing.m_value;
}
public int accessReflection(int loops) throwsException {
TimingClass timing = new TimingClass();
try {
Field field = TimingClass.class.
getDeclaredField("m_value");
for (int index = 0; index < loops; index++) {
int value = (field.getInt(timing) +
ADDITIVE_VALUE) * MULTIPLIER_VALUE;
field.setInt(timing, value);
}
return timing.m_value;
} catch (Exception ex) {
System.out.println("Error using reflection");
throw ex;
}
}
在上面的例子中,测试程序重复调用每种方法,使用一个大循环数,从而平均多次调用的时
间衡量结果。平均值中不包括每种方法第一次调用的时间,因此初始化时间不是结果中的一
个因素。下面的图清楚的向我们展示了每种方法字段接入的时间:
图 1:字段接入时间 :
我们可以看出:在前两副图中(Sun JVM),使用反射的执行时间超过使用直接接入的 1000
倍以上。通过比较,IBM JVM 可能稍好一些,但反射方法仍旧需要比其它方法长 700 倍以
上的时间。 任何 JVM 上其它两种方法之间时间方面无任何显著差异, 但IBM JVM 几乎比Sun
JVM 快一倍。最有可能的是这种差异反映了 Sun Hot SpotJVM 的专业优化,它在简单基准
方面表现得很糟糕。反射性能是 Sun 开发 1.4 JVM 时关注的一个方面,它在反射方法调用
结果中显示。在这类操作的性能方面,Sun 1.4.1 JVM 显示了比 1.3.1版本很大的改进。
如果为为创建使用反射的对象编写了类似的计时测试程序, 我们会发现这种情况下的差异不
象字段和方法调用情况下那么显著。 使用newInstance()调 用创建一个简单的java.lang.Object
实例耗用的时间大约是在 Sun 1.3.1 JVM 上使用 new Object()的 12 倍,是在 IBM 1.4.0 JVM
的四倍,只是 Sun 1.4.1 JVM 上的两部。使用 Array.newInstance(type,size)创建一个数组耗用
的时间是任何测试的 JVM 上使用 new type[size]的两倍,随着数组大小的增加,差异逐步缩
小。随着 jdk6.0 的推出,反射机制的性能也有了很大的提升。期待中… .
总结:
Java语言反射提供一种动态链接程序组件的多功能方法。 它允许程序创建和控制任何类的对
象(根据安全性限制),无需提前硬编码目标类。这些特性使得反射 特别适用于创建以非常
普通的方式与对象协作的库。例如,反射经常在持续存储对象为数据库、XML 或其它外部
格式的框架中使用。Javareflection 非常有用,它使类和数据结构能按名称动态检索相关信
息,并允许在运行着的程序中操作这些信息。Java 的这一特性非常强大,并且是其它一些
常用语言,如 C、C++、Fortran 或者 Pascal 等都不具备的。
但反射有两个缺点。第一个是性能问题。用于字段和方法接入时反射要远慢于直接代码。性
能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相 对很少涉及的部
分,缓慢的性能将不会是一个问题。即使测试中最坏情况下的计时图显示的反射操作只耗用
几微秒。仅反射在性能关键的应用的核心逻辑中使用时性 能问题才变得至关重要。
许多应用中更严重的一个缺点是使用反射会模糊程序内部实际要发生的事情。 程序人员希望
在源代码中看到程序的逻辑,反射等绕过了源代码的技术会带来维护问 题。反射代码比相
应的直接代码更复杂,正如性能比较的代码实例中看到的一样。解决这些问题的最佳方案是
保守地使用反射——仅在它可以真正增加灵活性的地方 ——记录其在目标类中的使用。
一下是对应各个部分的例子:
0 0
原创粉丝点击