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

问题描述 投票:0回答:8

我正在开发一个实用程序类来处理来自 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 reflection
8个回答
14
投票

听起来好像您想建立一个必须实现特定方法的合约。

在 Java 中,您通常通过接口来执行此操作:

public interface Frobnicator {
  public void frobnicate();
}

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

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

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

编辑关于更新:不,您不能让 Java 编译器静态检查此类代码。您可能需要编写自己的工具来为您进行检查。


3
投票

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

}

3
投票

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


2
投票

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

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


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

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


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

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

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 构造函数的调用,以找到第一个参数来检查相应命名的方法是否存在.

© www.soinside.com 2019 - 2024. All rights reserved.