方法查找工具2007-05-29 yycnet.yeah.net yyc译第11章介绍了Java 1.1新的“反射”概念,并利用这个概念查询一个特定类的方法——要么是由所有方法构成的一个完整列表,要么是这个列表的一个子集(名字与我们指定的关键字相符)。那个例子最大的好处就是能自动显示出所有方法,不强迫我们在继承结构中遍历,检查每一级的基础类。所以,它实际是我们节省编程时间的一个有效工具:因为大多数Java方法的名字都规定得非常全面和详尽,所以能有效地找出那些包含了一个特殊关键字的方法名。若找到符合标准的一个名字,便可根据它直接查阅联机帮助文档。
但第11的那个例子也有缺陷,它没有使用AWT,仅是一个纯命令行的应用。在这儿,我们准备制作一个改进的GUI版本,能在我们键入字符的时候自动刷新输出,也允许我们在输出结果中进行剪切和粘贴操作:
//: DisplayMethods.java// Display the methods of any class inside// a window. Dynamically narrows your search.import java.awt.*;import java.awt.event.*;import java.applet.*;import java.lang.reflect.*;import java.io.*;public class DisplayMethods extends Applet {Class cl;Method[] m;Constructor[] ctor;String[] n = new String[0];TextField name = new TextField(40),searchFor = new TextField(30);Checkbox strip = new Checkbox("Strip Qualifiers");TextArea results = new TextArea(40, 65);public void init() {strip.setState(true);name.addTextListener(new NameL());searchFor.addTextListener(new SearchForL());strip.addItemListener(new StripL());Panel top = new Panel(),lower = new Panel(),p = new Panel();top.add(new Label("Qualified class name:"));top.add(name);lower.add(new Label("String to search for:"));lower.add(searchFor);lower.add(strip);p.setLayout(new BorderLayout());p.add(top, BorderLayout.NORTH);p.add(lower, BorderLayout.SOUTH);setLayout(new BorderLayout());add(p, BorderLayout.NORTH);add(results, BorderLayout.CENTER);}class NameL implements TextListener {public void textValueChanged(TextEvent e) {String nm = name.getText().trim();if(nm.length() == 0) {results.setText("No match");n = new String[0];return;}try {cl = Class.forName(nm);} catch (ClassNotFoundException ex) {results.setText("No match");return;}m = cl.getMethods();ctor = cl.getConstructors();// Convert to an array of Strings:n = new String[m.length + ctor.length];for(int i = 0; i < m.length; i++)n[i] = m[i].toString();for(int i = 0; i < ctor.length; i++)n[i + m.length] = ctor[i].toString();reDisplay();}}void reDisplay() {// Create the result set:String[] rs = new String[n.length];String find = searchFor.getText();int j = 0;// Select from the list if find exists:for (int i = 0; i < n.length; i++) {if(find == null)rs[j++] = n[i];else if(n[i].indexOf(find) != -1)rs[j++] = n[i];}results.setText("");if(strip.getState() == true)for (int i = 0; i < j; i++)results.append(StripQualifiers.strip(rs[i]) + "
");else // Leave qualifiers onfor (int i = 0; i < j; i++)results.append(rs[i] + "
");}class StripL implements ItemListener {public void itemStateChanged(ItemEvent e) {reDisplay();}}class SearchForL implements TextListener {public void textValueChanged(TextEvent e) {reDisplay();}}public static void main(String[] args) {DisplayMethods applet = new DisplayMethods();Frame aFrame = new Frame("Display Methods");aFrame.addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {System.exit(0);}});aFrame.add(applet, BorderLayout.CENTER);aFrame.setSize(500,750);applet.init();applet.start();aFrame.setVisible(true);}}class StripQualifiers {private StreamTokenizer st;public StripQualifiers(String qualified) {st = new StreamTokenizer(new StringReader(qualified));st.ordinaryChar(" ");}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;}} ///:~
程序中的有些东西已在以前见识过了。和本书的许多GUI程序一样,这既可作为一个独立的应用程序使用,亦可作为一个程序片(Applet)使用。此外,StripQualifiers类与它在第11章的表现是完全一样的。
GUI包含了一个名为name的“文本字段”(TextField),或在其中输入想查找的类名;还包含了另一个文本字段,名为searchFor,可选择性地在其中输入一定的文字,希望在方法列表中查找那些文字。Checkbox(复选框)允许我们指出最终希望在输出中使用完整的名字,还是将前面的各种限定信息删去。最后,结果显示于一个“文本区域”(TextArea)中。
大家会注意到这个程序未使用任何按钮或其他组件,不能用它们开始一次搜索。这是由于无论文本字段还是复选框都会受到它们的“侦听者(Listener)对象的监视。只要作出一项改变,结果列表便会立即更新。若改变了name字段中的文字,新的文字就会在NameL类中捕获。若文字不为空,则在Class.forName()中用于尝试查找类。当然,在文字键入期间,名字可能会变得不完整,而Class.forName()会失败,这意味着它会“掷”出一个违例。该违例会被捕获,TextArea会随之设为“Nomatch”(没有相符)。但只要键入了一个正确的名字(大小写也算在内),Class.forName()就会成功,而getMethods()和getConstructors()会分别返回由Method和Constructor对象构成的一个数组。这些数组中的每个对象都会通过toString()转变成一个字串(这样便产生了完整的方法或构建器签名),而且两个列表都会合并到n中——一个独立的字串数组。数组n属于DisplayMethods类的一名成员,并在调用reDisplay()时用于显示的更新。
若改变了Checkbox或searchFor组件,它们的“侦听者”会简单地调用reDisplay()。reDisplay()会创建一个临时数组,其中包含了名为rs的字串(rs代表“结果集”——Result Set)。结果集要么直接从n复制(没有find关键字),要么选择性地从包含了find关键字的n中的字串复制。最后会检查strip Checkbox,看看用户是不是希望将名字中多余的部分删除(默认为“是”)。若答案是肯定的,则用StripQualifiers.strip()做这件事情;反之,就将列表简单地显示出来。
在init()中,大家也许认为在设置布局时需要进行大量繁重的工作。事实上,组件的布置完全可能只需要极少的工作。但象这样使用BorderLayout的好处是它允许用户改变窗口的大小,并特别能使TextArea(文本区域)更大一些,这意味着我们可以改变大小,以便毋需滚动即可看到更长的名字。
编程时,大家会发现特别有必要让这个工具处于运行状态,因为在试图判断要调用什么方法的时候,它提供了最好的方法之一。