电话:0731-83595998
导航

Java语言深入--深入浅析Java的反射机制

来源: 2017-12-21 10:59

  有时候我们说某个语言具有很强的动态性,有时候我们会区分动态和静态的不同技术与作法。我们朗朗上口动态绑定(dynamic binding)、动态链接(dynamic linking)、动态加载(dynamic loading)等。然而"动态"一词其实没有绝对而普遍适用的严格定义,有时候甚至像对象导向当初被导入编程领域一样,一人一把号,各吹各的调。

   
 
 
 
众所周知Java有个Object class,是所有Java classes的继承根源,其内声明了数个应该在所有Java class中被改写的methods:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()返回一个Class object。
Class class十分特殊。它和一般classes一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class object。如果您想借由"修改Java标准库源码"来观察Class object的实际生成时机(例如在Class的constructor内添加一个println()),不能够!因为Class并没有public constructor(见图1)。最后我会拨一小块篇幅顺带谈谈Java标准库源码的改动办法。
Class是Reflection故事起源。针对任何您想探勘的class,唯有先为它产生一个Class object,接下来才能经由后者唤起为数十多个的Reflection APIs。这些APIs将在稍后的探险活动中一一亮相。
#001 public final
#003 java.lang.reflect.GenericDeclaration,
#005 java.lang.reflect.AnnotatedElement {
#007 public String toString() {
#009 (isPrimitive() ? "" : "class "))
#011 }
Class class片段。注意它的private empty ctor,意指不允许任何人经由编程方式产生Class object。是的,其object 只能由JVM 产生。
"Class" object的取得途径
Class object 诞生管道 示例
注:每个class 都有此函数 String str = "abc";
运用
Class c1 = b.getClass();
运用static method
(最常被使用) Class c1 = Class.forName ("java.lang.String");
Class c3 = Class.forName ("java.util.LinkedList$Entry");
Class c5 = Class.forName ("[I");
.class 语法 Class c1 = String.class;
Class c3 = Main.InnerClass.class;
Class c5 = int.class;
primitive wrapper classes
Class c1 = Boolean.TYPE;
Class c3 = Character.TYPE;
Class c5 = Integer.TYPE;
Class c7 = Float.TYPE;
Class c9 = Void.TYPE;
首先容我以图3的java.util.LinkedList为例,将Java class的定义大卸八块,每一块分别对应图4所示的Reflection API。图5则是"获得class各区块信息"的程序示例及执行结果,它们都取自示例程序的对应片段。
package java.util; //(1)
public class LinkedList //(3)(4)(5)
implements List, Queue,
{
public LinkedList() { … } //(9)
public E getFirst() { … } //(10)
private transient Entry header = …; //(11)
}

图3的各个Java class成份,分别对应于图4的Reflection API,其中出现的Package、Method、Constructor、Field等等classes,都定义于java.lang.reflect。
(1) package class隶属哪个package getPackage() Package
解决办法见图5-2。 
int getModifiers()
Modifier.isInterface(int) int
bool
(5) type parameters 参数化类型的名称 getTypeParameters() TypeVariable 
(7) implemented interfaces 实现有哪些interfaces getInterfaces() Class
(8) inner classes 内部classes getDeclaredClasses() Class
(9) constructors 构造函数getDeclaredConstructors() 不论 public 或private 或其它access level,皆可获得。另有功能近似之取得函数。 Constructor
(11) fields 字段(成员变量) getDeclaredFields()不论 public 或private 或其它access level,皆可获得。另有功能近似之取得函数。 Field
Reflection APIs 的全部。
Java Reflection API 运用示例

#002 c = Class.forName(args[0]);
#004 Package p;
#006
#008 System.out.println("package "+p.getName()+";");
执行结果(例):
图5-1:找出class 隶属的package。其中的c将继续沿用于以下各程序片段。
#001 ff = c.getDeclaredFields();
#003 x = tName(ff[i].getType().getName(), classRef);
#005 cn = c.getDeclaredConstructors();
#007 Class cx = cn[i].getParameterTypes();
#009 x = tName(cx[j].getName(), classRef);
#011
#013 for (int i = 0; i < mm.length; i++) {
#015 Class cx = mm[i].getParameterTypes();
#017 x = tName(cx[j].getName(), classRef);
#019 classRef.remove(c.getName()); //不必记录自己(不需import 自己)
执行结果(例):
import java.lang.Object;
import java.util.Collection;
import java.io.ObjectInputStream;

#002 System.out.print(Modifier.toString(mod)); //整个modifier
#004 if (Modifier.isInterface(mod))
#006 else
#008 System.out.print(tName(c.getName(), null)); //class 名称
执行结果(例):
图5-3:找出class或interface 的名称,及其属性(modifiers)。
#001 TypeVariable tv;
#003 for (int i = 0; i < tv.length; i++) {
#005 if (i == 0) //第一个
#011 }
执行结果(例):
或 public class LinkedList

#002 supClass = c.getSuperclass();
#004 System.out.print(" extends" +

public class LinkedList

#002 Class ctmp;
#004 cc = c.getInterfaces();
#006 System.out.print(", "r"n" + " implements "); //关键词
#008 System.out.print(tName(cite.getName(), null)+", ");
执行结果(例):
extends AbstractSequentialList,
图5-6:找出implemented interfaces。执行结果多出一个不该有的逗号于尾端。此非本处重点,为简化计,不多做处理。
#001 cc = c.getDeclaredClasses(); //找出inner classes
#003 System.out.println(tName(cite.getName(), null));
#005 ctmp = c.getDeclaringClass(); //找出outer classes
#007 System.out.println(ctmp.getName());
执行结果(例):
LinkedList$ListItr

#002 cn = c.getDeclaredConstructors();
#004 int md = cn[i].getModifiers();
#006 cn[i].getName());
#008 System.out.print("(");
#010 System.out.print(tName(cx[j].getName(), null));
#012 }
#014 }
执行结果(例):
public java.util.LinkedList()


public java.util.LinkedList(java.util.Collection)
图5-8b:找出所有constructors。本例在for 循环内使用toGenericString(),省事。
#001 Method mm;
#003 for (int i = 0; i < mm.length; i++) {
#005 System.out.print(" "+Modifier.toString(md)+" "+
#007 mm[i].getName());
#009 System.out.print("(");
#011 System.out.print(tName(cx[j].getName(), null));
#013 }
#015 }
执行结果(例):
public int size()


public int java.util.LinkedList.size()

#002 ff = c.getDeclaredFields();
#004 int md = ff[i].getModifiers();
#006 tName(ff[i].getType().getName(), null) +" "+
#008 }
执行结果(例):
private transient int size;


java.util.LinkedList.header
找出所有fields。本例在for 循环内使用toGenericString(),省事。|||   找出class参用(导入)的所有classes

行期唤起methods,(3) 运行时改动fields。
运行时生成instances
一个针对"带参数ctor"。图6是面对"无自变量ctor"的例子。如果欲调用的是"带参数ctor"就比较麻烦些,图7是个例子,其中不再调用Class的newInstance(),而是调用Constructor 的newInstance()。图7首先准备一个Class做为ctor的参数类型(本例指定为一个double和一个int),然后以此为自变量调用getConstructor(),获得一个专属ctor。接下来再准备一个Object 做为ctor实参值(本例指定3.14159和125),调用上述专属ctor的newInstance()。
#001 Class c = Class.forName("DynTest");
#003 obj = c.newInstance(); //不带自变量
图6:动态生成"Class object 所对应之class"的对象实体;无自变量。
#001 Class c = Class.forName("DynTest");
#003 Constructor ctor = c.getConstructor(pTypes);
#005
#007 Object arg = new Object {3.14159, 125}; //自变量
#009 System.out.println(obj);

这个动作和上述调用"带参数之ctor"相当类似。首先准备一个Class做为ctor的参数类型(本例指定其中一个是String,另一个是Hashtable),然后以此为自变量调用getMethod(),获得特定的Method object。接下来准备一个Object放置自变量,然后调用上述所得之特定Method object的invoke(),如图8。知道为什么索取Method object时不需指定回返类型吗?因为method overloading机制要求signature(署名式)必须唯一,而回返类型并非signature的一个成份。换句话说,只要指定了method名称和参数列,就一定指出了一个独一无二的method。
#001 public String func(String s, Hashtable ht)
#003 …System.out.println("func invoked"); return s;
#005 public static void main(String args)
#007 Class c = Class.forName("Test");
#009 ptypes[0] = Class.forName("java.lang.String");
#011 Method m = c.getMethod("func",ptypes);
#013 Object args = new Object;
#015 arg[1] = null;
#017 Integer rval = (String)r;
#019 }

与先前两个动作相比,"变更field内容"轻松多了,因为它不需要参数和自变量。首先调用Class的getField()并指定field名称。获得特定的Field object之后便可直接调用Field的get()和set()。
#001 public class Test {
#003
#005 {
#007 Field f = c.getField("d"); //指定field 名称
#009 System.out.println("d= " + (Double)f.get(obj));
#011 System.out.println("d= " + obj.d);
#013 }

先前我曾提到,原本想借由"改动Java标准库源码"来测知Class object的生成,但由于其ctor原始设计为private,也就是说不可能透过这个管道生成Class object(而是由class loader负责生成),因此"在ctor中打印出某种信息"的企图也就失去了意义。
这里我要谈点题外话:如何修改Java标准库源码并让它反应到我们的应用程序来。假设我想修改java.lang.Class,让它在某些情况下打印某种信息。首先必须找出标准源码!当你下载JDK 套件并安装妥当,你会发现jdk150"src"java"lang 目录之中有Class.java,这就是我们此次行动的标准源码。备份后加以修改,编译获得Class.class。接下来准备将.class 搬移到jdk150"jre"lib"endorsed。
这是一个十分特别的目录,class loader将优先从该处读取内含classes的.jar文件??成功的条件是.jar内的classes压缩路径必须和Java标准库的路径完全相同。为此,我们可以将刚才做出的Class.class先搬到一个为此目的而刻意做出来的"java"lang目录中,压缩为foo.zip(任意命名,唯需夹带路径java"lang),再将这个foo.zip搬到jdk150"jre"lib"endorsed并改名为foo.jar。此后你的应用程序便会优先用上这里的java.lang.Class。整个过程可写成一个批处理文件(batch file),在DOS Box中使用。

  JDK1.5 安装后的目录组织。其中的endorsed 是我新建。
del e:"java"lang"*.class //清理干净
c:
javac -Xlint:unchecked Class.java //编译源码
move *.class e:"java"lang //搬移至刻意制造的目录中
cd e:"java"lang //以下压缩至适当目录
cd e:"test //进入测试目录
java Test //执行测试程序
的修改动作。Pkzipc(.exe)是个命令列压缩工具,add和path都是其命令。

编辑推荐:

下载Word文档

温馨提示:因考试政策、内容不断变化与调整,长理培训网站提供的以上信息仅供参考,如有异议,请考生以权威部门公布的内容为准! (责任编辑:长理培训)

网络课程 新人注册送三重礼

已有 22658 名学员学习以下课程通过考试

网友评论(共0条评论)

请自觉遵守互联网相关政策法规,评论内容只代表网友观点!

最新评论

点击加载更多评论>>

精品课程

更多
10781人学习

免费试听更多

相关推荐
图书更多+
  • 电网书籍
  • 财会书籍
  • 其它工学书籍
拼团课程更多+
  • 电气拼团课程
  • 财会拼团课程
  • 其它工学拼团
热门排行

长理培训客户端 资讯,试题,视频一手掌握

去 App Store 免费下载 iOS 客户端