Java:检查编译时是否存在给定的方法名称

时间:2010-04-09 13:27:05

标签: java compiler-construction

我正在开发一个实用程序类来处理来自Java Swing组件的Actions; 我想知道是否有一种方法可以检查给定的方法名称(将通过反射访问)是否在编译时存在,如果没有则显示编译器错误?

- 更新

好的,看起来我不清楚,让我们谈谈细节:

我有一个名为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的参数,我将在运行时看到它。我想在编译时看到它。得到它了? :)

顺便说一下,这个班级工作得很好,我只是想做这个改进。

- 更新

学习Annotations方法

8 个答案:

答案 0 :(得分:14)

听起来好像你想建立一个必须实施特定方法的合同。

在Java中,您通常通过界面执行此操作:

public interface Frobnicator {
  public void frobnicate();
}

然后在您的其他代码中,您只需要获得该类型的对象,这样编译器将验证该方法是否存在:

public void frobnicateorize(Frobnicator forb) {
  frob.frobnicate();
}

这样你甚至可以避免使用反射来调用方法。

关于更新的

编辑:不,您不能让Java编译器静态检查这种代码。您可能必须编写自己的工具来为您进行检查。

答案 1 :(得分:3)

没有。没有办法做到这一点。

答案 2 :(得分:2)

我不知道如何使用任何现有软件来做到这一点。

但是,假设方法名称可以在编译时解析(在这种情况下,您似乎不太可能使用反射),您可以设想创建一个IDE插件来执行此操作。

答案 3 :(得分:2)

您可以使用注释而不是字符串文字来表示调用的操作,然后使用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
    }

}

答案 4 :(得分:2)

在编译时无法检查动态方法。但是,有可能在某种程度上重新设计框架类以满足需要。 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);

它比您现有的实现更冗长,但它为您提供了所需的编译时类型安全性。

答案 5 :(得分:0)

可以反思:

        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();
        }

答案 6 :(得分:0)

使用“常量”

只要它是正确的类型,编译器在传递字符串文字时不会抱怨。

但是,将变量作为参数传递时,必须至少定义变量。

您可以为每个操作定义一些类变量“常量”,并建议调用者使用这些变量。如果变量参数未定义,编译器将给出错误。

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(...);

答案 7 :(得分:0)

我认为你只需要声明一个回调接口来补充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构造函数的调用,以找到第一个参数来检查是否存在相应命名的方法