一个java类方法提取器2007-05-29 yycnet.yeah.net yyc译很少需要直接使用反射工具;之所以在语言中提供它们,仅仅是为了支持其他Java特性,比如对象序列化(第10章介绍)、Java Beans以及RMI(本章后面介绍)。但是,我们许多时候仍然需要动态提取与一个类有关的资料。其中特别有用的工具便是一个类方法提取器。正如前面指出的那样,若检视类定义源码或者联机文档,只能看到在那个类定义中被定义或覆盖的方法,基础类那里还有大量资料拿不到。幸运的是,“反射”做到了这一点,可用它写一个简单的工具,令其自动展示整个接口。下面便是具体的程序:
//: ShowMethods.java// Using Java 1.1 reflection to show all the // methods of a class, even if the methods are // defined in the base class.import java.lang.reflect.*;public class ShowMethods {static final String usage ="usage:
" +"ShowMethods qualified.class.name
" +"To show all methods in class or:
" +"ShowMethods qualified.class.name word
" +"To search for methods involving "word"";public static void main(String[] args) {if(args.length < 1) {System.out.println(usage);System.exit(0);}try {Class c = Class.forName(args[0]);Method[] m = c.getMethods();Constructor[] ctor = c.getConstructors();if(args.length == 1) {for (int i = 0; i < m.length; i++)System.out.println(m[i].toString());for (int i = 0; i < ctor.length; i++)System.out.println(ctor[i].toString());} else {for (int i = 0; i < m.length; i++)if(m[i].toString() .indexOf(args[1])!= -1)System.out.println(m[i].toString());for (int i = 0; i < ctor.length; i++)if(ctor[i].toString() .indexOf(args[1])!= -1)System.out.println(ctor[i].toString());}} catch (ClassNotFoundException e) {System.out.println("No such class: " + e);}}} ///:~
Class方法getMethods()和getConstructors()可以分别返回Method和Constructor的一个数组。每个类都提供了进一步的方法,可解析出它们所代表的方法的名字、参数以及返回值。但也可以象这样一样只使用toString(),生成一个含有完整方法签名的字串。代码剩余的部分只是用于提取命令行信息,判断特定的签名是否与我们的目标字串相符(使用indexOf()),并打印出结果。
这里便用到了“反射”技术,因为由Class.forName()产生的结果不能在编译期间获知,所以所有方法签名信息都会在运行期间提取。若研究一下联机文档中关于“反射”(Reflection)的那部分文字,就会发现它已提供了足够多的支持,可对一个编译期完全未知的对象进行实际的设置以及发出方法调用。同样地,这也属于几乎完全不用我们操心的一个步骤——Java自己会利用这种支持,所以程序设计环境能够控制Java Beans——但它无论如何都是非常有趣的。
一个有趣的试验是运行java ShowMehods ShowMethods。这样做可得到一个列表,其中包括一个public默认构建器,尽管我们在代码中看见并没有定义一个构建器。我们看到的是由编译器自动合成的那一个构建器。如果随之将ShowMethods设为一个非public类(即换成“友好”类),合成的默认构建器便不会在输出结果中出现。合成的默认构建器会自动获得与类一样的访问权限。
ShowMethods的输出仍然有些“不爽”。例如,下面是通过调用java ShowMethods java.lang.String得到的输出结果的一部分:
public boolean java.lang.String.startsWith(java.lang.String,int)public boolean java.lang.String.startsWith(java.lang.String)public booleanjava.lang.String.endsWith(java.lang.String)
若能去掉象java.lang这样的限定词,结果显然会更令人满意。有鉴于此,可引入上一章介绍的StreamTokenizer类,解决这个问题:
//: ShowMethodsClean.java// ShowMethods with the qualifiers stripped// to make the results easier to readimport java.lang.reflect.*;import java.io.*;public class ShowMethodsClean {static final String usage ="usage:
" +"ShowMethodsClean qualified.class.name
" +"To show all methods in class or:
" +"ShowMethodsClean qualif.class.name word
" +"To search for methods involving "word"";public static void main(String[] args) {if(args.length < 1) {System.out.println(usage);System.exit(0);}try {Class c = Class.forName(args[0]);Method[] m = c.getMethods();Constructor[] ctor = c.getConstructors();// Convert to an array of cleaned Strings:String[] n = new String[m.length + ctor.length];for(int i = 0; i < m.length; i++) {String s = m[i].toString();n[i] = StripQualifiers.strip(s);}for(int i = 0; i < ctor.length; i++) {String s = ctor[i].toString();n[i + m.length] = StripQualifiers.strip(s);}if(args.length == 1)for (int i = 0; i < n.length; i++)System.out.println(n[i]);elsefor (int i = 0; i < n.length; i++)if(n[i].indexOf(args[1])!= -1)System.out.println(n[i]);} catch (ClassNotFoundException e) {System.out.println("No such class: " + e);}}}class StripQualifiers {private StreamTokenizer st;public StripQualifiers(String qualified) {st = new StreamTokenizer(new StringReader(qualified));st.ordinaryChar(" "); // Keep the spaces}public String getNext() {String s = null;try {if(st.nextToken() !=StreamTokenizer.TT_EOF) {switch(st.ttype) {case StreamTokenizer.TT_EOL:s = null;break;case StreamTokenizer.TT_NUMBER:s = Double.toString(st.nval);break;case StreamTokenizer.TT_WORD:s = new String(st.sval);break;default: // single character in ttypes = String.valueOf((char)st.ttype);}}} catch(IOException e) {System.out.println(e);}return s;}public static String strip(String qualified) {StripQualifiers sq = new StripQualifiers(qualified);String s = "", si;while((si = sq.getNext()) != null) {int lastDot = si.lastIndexOf(".");if(lastDot != -1)si = si.substring(lastDot + 1);s += si;}return s;}} ///:~
ShowMethodsClean方法非常接近前一个ShowMethods,只是它取得了Method和Constructor数组,并将它们转换成单个String数组。随后,每个这样的String对象都在StripQualifiers.Strip()里“过”一遍,删除所有方法限定词。正如大家看到的那样,此时用到了StreamTokenizer和String来完成这个工作。
假如记不得一个类是否有一个特定的方法,而且不想在联机文档里逐步检查类结构,或者不知道那个类是否能对某个对象(如Color对象)做某件事情,该工具便可节省大量编程时间。
第17章提供了这个程序的一个GUI版本,可在自己写代码的时候运行它,以便快速查找需要的东西。