💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 17.2 方法查找工具 第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]) + "\n"); else // Leave qualifiers on for (int i = 0; i < j; i++) results.append(rs[i] + "\n"); } 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 ttype s = 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`(文本区域)更大一些,这意味着我们可以改变大小,以便毋需滚动即可看到更长的名字。 编程时,大家会发现特别有必要让这个工具处于运行状态,因为在试图判断要调用什么方法的时候,它提供了最好的方法之一。