17.2 方法查找工具

第11章介绍了Java 1.1新的“反射”概念,并利用这个概念查询一个特定类的方法——要么是由所有方法构成的一个完整列表,要么是这个列表的一个子集(名字与我们指定的关键字相符)。那个例子最大的好处就是能自动显示出所有方法,不强迫我们在继承结构中遍历,检查每一级的基类。所以,它实际是我们节省编程时间的一个有效工具:因为大多数Java方法的名字都规定得非常全面和详尽,所以能有效地找出那些包含了一个特殊关键字的方法名。若找到符合标准的一个名字,便可根据它直接查阅联机帮助文档。

但第11的那个例子也有缺陷,它没有使用AWT,仅是一个纯命令行的应用。在这儿,我们准备制作一个改进的GUI版本,能在我们键入字符的时候自动刷新输出,也允许我们在输出结果中进行剪切和粘贴操作:

  1. //: DisplayMethods.java
  2. // Display the methods of any class inside
  3. // a window. Dynamically narrows your search.
  4. import java.awt.*;
  5. import java.awt.event.*;
  6. import java.applet.*;
  7. import java.lang.reflect.*;
  8. import java.io.*;
  9. public class DisplayMethods extends Applet {
  10. Class cl;
  11. Method[] m;
  12. Constructor[] ctor;
  13. String[] n = new String[0];
  14. TextField
  15. name = new TextField(40),
  16. searchFor = new TextField(30);
  17. Checkbox strip =
  18. new Checkbox("Strip Qualifiers");
  19. TextArea results = new TextArea(40, 65);
  20. public void init() {
  21. strip.setState(true);
  22. name.addTextListener(new NameL());
  23. searchFor.addTextListener(new SearchForL());
  24. strip.addItemListener(new StripL());
  25. Panel
  26. top = new Panel(),
  27. lower = new Panel(),
  28. p = new Panel();
  29. top.add(new Label("Qualified class name:"));
  30. top.add(name);
  31. lower.add(
  32. new Label("String to search for:"));
  33. lower.add(searchFor);
  34. lower.add(strip);
  35. p.setLayout(new BorderLayout());
  36. p.add(top, BorderLayout.NORTH);
  37. p.add(lower, BorderLayout.SOUTH);
  38. setLayout(new BorderLayout());
  39. add(p, BorderLayout.NORTH);
  40. add(results, BorderLayout.CENTER);
  41. }
  42. class NameL implements TextListener {
  43. public void textValueChanged(TextEvent e) {
  44. String nm = name.getText().trim();
  45. if(nm.length() == 0) {
  46. results.setText("No match");
  47. n = new String[0];
  48. return;
  49. }
  50. try {
  51. cl = Class.forName(nm);
  52. } catch (ClassNotFoundException ex) {
  53. results.setText("No match");
  54. return;
  55. }
  56. m = cl.getMethods();
  57. ctor = cl.getConstructors();
  58. // Convert to an array of Strings:
  59. n = new String[m.length + ctor.length];
  60. for(int i = 0; i < m.length; i++)
  61. n[i] = m[i].toString();
  62. for(int i = 0; i < ctor.length; i++)
  63. n[i + m.length] = ctor[i].toString();
  64. reDisplay();
  65. }
  66. }
  67. void reDisplay() {
  68. // Create the result set:
  69. String[] rs = new String[n.length];
  70. String find = searchFor.getText();
  71. int j = 0;
  72. // Select from the list if find exists:
  73. for (int i = 0; i < n.length; i++) {
  74. if(find == null)
  75. rs[j++] = n[i];
  76. else if(n[i].indexOf(find) != -1)
  77. rs[j++] = n[i];
  78. }
  79. results.setText("");
  80. if(strip.getState() == true)
  81. for (int i = 0; i < j; i++)
  82. results.append(
  83. StripQualifiers.strip(rs[i]) + "\n");
  84. else // Leave qualifiers on
  85. for (int i = 0; i < j; i++)
  86. results.append(rs[i] + "\n");
  87. }
  88. class StripL implements ItemListener {
  89. public void itemStateChanged(ItemEvent e) {
  90. reDisplay();
  91. }
  92. }
  93. class SearchForL implements TextListener {
  94. public void textValueChanged(TextEvent e) {
  95. reDisplay();
  96. }
  97. }
  98. public static void main(String[] args) {
  99. DisplayMethods applet = new DisplayMethods();
  100. Frame aFrame = new Frame("Display Methods");
  101. aFrame.addWindowListener(
  102. new WindowAdapter() {
  103. public void windowClosing(WindowEvent e) {
  104. System.exit(0);
  105. }
  106. });
  107. aFrame.add(applet, BorderLayout.CENTER);
  108. aFrame.setSize(500,750);
  109. applet.init();
  110. applet.start();
  111. aFrame.setVisible(true);
  112. }
  113. }
  114. class StripQualifiers {
  115. private StreamTokenizer st;
  116. public StripQualifiers(String qualified) {
  117. st = new StreamTokenizer(
  118. new StringReader(qualified));
  119. st.ordinaryChar(' ');
  120. }
  121. public String getNext() {
  122. String s = null;
  123. try {
  124. if(st.nextToken() !=
  125. StreamTokenizer.TT_EOF) {
  126. switch(st.ttype) {
  127. case StreamTokenizer.TT_EOL:
  128. s = null;
  129. break;
  130. case StreamTokenizer.TT_NUMBER:
  131. s = Double.toString(st.nval);
  132. break;
  133. case StreamTokenizer.TT_WORD:
  134. s = new String(st.sval);
  135. break;
  136. default: // single character in ttype
  137. s = String.valueOf((char)st.ttype);
  138. }
  139. }
  140. } catch(IOException e) {
  141. System.out.println(e);
  142. }
  143. return s;
  144. }
  145. public static String strip(String qualified) {
  146. StripQualifiers sq =
  147. new StripQualifiers(qualified);
  148. String s = "", si;
  149. while((si = sq.getNext()) != null) {
  150. int lastDot = si.lastIndexOf('.');
  151. if(lastDot != -1)
  152. si = si.substring(lastDot + 1);
  153. s += si;
  154. }
  155. return s;
  156. }
  157. } ///:~

程序中的有些东西已在以前见识过了。和本书的许多GUI程序一样,这既可作为一个独立的应用程序使用,亦可作为一个程序片(Applet)使用。此外,StripQualifiers类与它在第11章的表现是完全一样的。

GUI包含了一个名为name的“文本字段”(TextField),或在其中输入想查找的类名;还包含了另一个文本字段,名为searchFor,可选择性地在其中输入一定的文字,希望在方法列表中查找那些文字。Checkbox(复选框)允许我们指出最终希望在输出中使用完整的名字,还是将前面的各种限定信息删去。最后,结果显示于一个“文本区域”(TextArea)中。

大家会注意到这个程序未使用任何按钮或其他组件,不能用它们开始一次搜索。这是由于无论文本字段还是复选框都会受到它们的“监听者(Listener)对象的监视。只要作出一项改变,结果列表便会立即更新。若改变了name字段中的文字,新的文字就会在NameL类中捕获。若文字不为空,则在Class.forName()中用于尝试查找类。当然,在文字键入期间,名字可能会变得不完整,而Class.forName()会失败,这意味着它会“抛”出一个异常。该异常会被捕获,TextArea会随之设为Nomatch(不相符)。但只要键入了一个正确的名字(大小写也算在内),Class.forName()就会成功,而getMethods()getConstructors()会分别返回由MethodConstructor对象构成的一个数组。这些数组中的每个对象都会通过toString()转变成一个字符串(这样便产生了完整的方法或构造器签名),而且两个列表都会合并到n中——一个独立的字符串数组。数组n属于DisplayMethods类的一名成员,并在调用reDisplay()时用于显示的更新。

若改变了CheckboxsearchFor组件,它们的“监听者”会简单地调用reDisplay()reDisplay()会创建一个临时数组,其中包含了名为rs的字符串(rs代表“结果集”——Result Set)。结果集要么直接从n复制(没有find关键字),要么选择性地从包含了find关键字的n中的字符串复制。最后会检查strip Checkbox,看看用户是不是希望将名字中多余的部分删除(默认为“是”)。若答案是肯定的,则用StripQualifiers.strip()做这件事情;反之,就将列表简单地显示出来。

init()中,大家也许认为在设置布局时需要进行大量繁重的工作。事实上,组件的布置完全可能只需要极少的工作。但象这样使用BorderLayout的好处是它允许用户改变窗口的大小,并特别能使TextArea(文本区域)更大一些,这意味着我们可以改变大小,以便毋需滚动即可看到更长的名字。

编程时,大家会发现特别有必要让这个工具处于运行状态,因为在试图判断要调用什么方法的时候,它提供了最好的方法之一。