我正在开发一个实用程序类来处理来自 Java Swing 组件的操作; 我想知道是否有一种方法可以检查编译时是否存在给定的方法名称(将通过反射访问),如果不存在则显示编译器错误?
--更新
好吧,看来我没说清楚,我们来说说细节吧:
我有一个名为 TomAction 的类,我用它来简化项目中的简单操作声明。而不是写这样的东西:
class MyClass {
public MyClass() {
Icon icon = null; // putting null to simplify the example
JButton btn = new JButton(new AbstractAction("Click to go!", icon) {
public void actionPerformed(ActionEvent ev) {
try {
go();
} catch (Exception e) {
e.printStackTrace();
String msg = "Fail to execute 'go' task.";
JOptionPane.showMessageDialog(null, msg, "Fail", JOptionPane.ERROR_MESSAGE);
}
}
});
}
private void go() {
// do task
}
}
..我只是写:
class MyClass {
public MyClass() {
String msg = "Fail to execute 'go' task.";
Icon icon = null; // putting null to simplify the example
TomAction act = new TomAction("go", this, "Click to go!", msg, icon);
JButton btn = new JButton(act);
}
private void go() {
// do task
}
}
..并且程序具有相同的行为。
问题是,如果我输入错误的方法名称作为 TomAction 的参数,我只会在运行时看到它。我想在编译时看到它。知道了? :)
顺便说一下,这个类运行得很好,我只是想做这个增强。
--更新
研究注释方法
听起来好像您想建立一个必须实现特定方法的合约。
在 Java 中,您通常通过接口来执行此操作:
public interface Frobnicator {
public void frobnicate();
}
然后在您的其他代码中,您只需获取该类型的对象,这样编译器将验证该方法是否存在:
public void frobnicateorize(Frobnicator forb) {
frob.frobnicate();
}
这样你甚至可以避免使用反射来调用方法。
编辑关于更新:不,您不能让 Java 编译器静态检查此类代码。您可能需要编写自己的工具来为您进行检查。
您可以使用注释而不是字符串文字来表示调用的操作,然后使用 APT 来验证该方法是否存在。 APT 由 javac 自动调用。
http://java.sun.com/javase/6/docs/technotes/tools/windows/javac.html#processing http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html http://java.sun.com/j2se/1.5.0/docs/guide/apt/mirror/overview-summary.html
通过您的示例,我建议如下设计 - 您在代码中放置绑定注释,并在注释处理器中添加操作实例化代码。在这种情况下的操作需要是类成员,无论如何这是一个很好的做法。确保对图标和文本使用资源包,从长远来看,你会省去很多麻烦:
class MyClass {
// the keys should be looked up from resource bundle
@TomActionBinding("go", "text-key", "msg-key", "icon-key");
Action act;
public MyClass() {
JButton btn = new JButton(act);
}
private void go() {
// do task
}
}
不。没有办法做到这一点。
我不知道如何使用任何现有软件来做到这一点。
但是,假设方法名称可以在编译时解析(在这种情况下,您似乎不太可能使用反射),您可以想象创建一个 IDE 插件来执行此操作。
无法在编译时检查动态方法。然而,也许可以重新设计你的框架类来完成你想要的事情。除了
TomAction
之外,您的 AbstractAction
类真正提供的唯一功能是错误处理和重命名 actionPerformed
方法的能力,但代价是使用反射和失去类型安全性(恕我直言,成本很高)。
我会这样实现:
public class TomAction extends AbstractAction {
public static interface Command {
// CommandException is implemented elsewhere
public void execute() throws CommandException;
}
private Command cmd;
// other members omitted for brevity
public TomAction(Command cmd, String name, String failMsg, Icon icon) {
super(name, icon);
this.cmd = cmd;
this.failMsg = failMsg;
}
public void actionPerformed(ActionEvent e) {
try {
cmd.execute();
}
catch (CommandException e) {
// handle failure
}
}
// remaining implementation
}
然后使用它:
TomAction.Command goCmd = new TomAction.Command() {
public void execute() throws CommandException {
go();
}
};
TomAction act = new TomAction(goCmd, "Click to go!", msg, icon);
JButton btn = new JButton(act);
它比您现有的实现更详细一些,但它为您提供了所需的编译时类型安全性。
通过反射是可能的:
Object o = object;
try {
Method m = o.getClass().getMethod("methodName");
m.invoke(o);
} catch (NoSuchMethodException e) {
//Method is not available
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
使用“常量”
只要类型正确,编译器在传递字符串文字时就不会抱怨。
但是,当将变量作为参数传递时,该变量至少必须被定义。
您可以为每个操作定义一些类变量“常量”,并建议调用者使用这些变量。如果变量参数未定义,编译器会给你一个错误。
public class TomAction {
public static final String GO_ACTION = "go";
...
}
要调用它,而不是这样做:
TomAction act = new TomAction("go", ...);
这样做:
TomAction act = new TomAction(TomAction.GO_ACTION, ...);
如果您尝试使用未定义的变量:
TomAction act = new TomAction(TomAction.WRONG_ACTION, ...);
你会得到错误:
TestClass.WRONG_CONSTANT cannot be resolved
使用对象类型
为了扩展传递变量的思想,为什么不传入对象,并要求对象是正确的类型呢?这样,他们就不再传递 String,所以他们绝对不能传递未定义的东西。
public class ActionType {
public String action;
}
public class GoAction extends ActionType {
public GoAction() {
this.action = "go";
}
}
public class TomAction {
public TomAction(ActionType actionType, ...) {
// Use actionType.action
...
}
...
}
要调用它,您可以这样做:
TomAction act = new TomAction(new GoAction(), ...);
当然这导致我们...
延长课程
扩展类,而不是使用参数。
public class GoAction extends TomAction {
}
然后这样做:
TomAction act = new GoAction(...);
我认为你只需要声明一个回调接口来补充你的 TomAction 类作为你的 TomAction “框架”的一部分:
interface TACB { // TomActionCallback
void go();
}
那么您的示例用法将变为:
class MyClass implements TACB {
public MyClass() {
String msg = "Fail to execure 'go' task.";
Icon icon = null; // putting null to simplify the example
TomAction act = new TomAction(this, this, "Click to go!", msg, icon);
JButton btn = new JButton(act);
}
public void go() {
// do task
}
}
或者:
class MyClass {
public MyClass() {
String msg = "Fail to execure 'go' task.";
Icon icon = null; // putting null to simplify the example
TACB tacb1 = new TACB() { public void go() { this.go1() } };
TomAction act1 = new TomAction(tacb1, this, "Click to go!", msg, icon);
JButton btn1 = new JButton(act1);
TACB tacb2 = new TACB() { public void go() { this.go2() } };
TomAction act2 = new TomAction(tacb2, this, "Click to go!", msg, icon);
JButton btn2 = new JButton(act2);
}
private void go1() {
// do task
}
private void go2() {
// do task
}
}
是的,它有点复杂,但这就是编译器检查的成本。 如果您仍然不喜欢这个解决方案,我唯一能想到的就是编写自己的工具来扫描类文件并剖析对 TomAction 构造函数的调用,以找到第一个参数来检查相应命名的方法是否存在.