Java: Проверьте, существует ли данное имя метода во время компиляции - PullRequest
5 голосов
/ 09 апреля 2010

Я разрабатываю служебный класс для обработки действий из компонентов 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, я увижу его только во время выполнения. Я хотел бы видеть это во время компиляции. Понял? :)

Кстати, этот класс работает очень хорошо, я просто хочу сделать это улучшение.

- обновление

изучение подхода Аннотации

Ответы [ 8 ]

14 голосов
/ 09 апреля 2010

Звучит так, как если бы вы хотели заключить договор о том, что должен быть реализован определенный метод.

В Java вы обычно делаете это через интерфейс:

public interface Frobnicator {
  public void frobnicate();
}

Затем в другом коде вы просто ожидаете получить объект этого типа, таким образом компилятор проверит, что метод существует:

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

Таким образом, вы даже можете избежать использования отражения для вызова метода.

Редактировать относительно обновления: Нет, такой код не может быть статически проверен компилятором Java. Возможно, вам придется написать свой собственный инструмент, чтобы выполнить эту проверку для вас.

3 голосов
/ 09 апреля 2010

Нет. Нет способа сделать это.

2 голосов
/ 10 апреля 2010

Нет способа проверить динамический метод во время компиляции. Тем не менее, может быть возможно немного изменить дизайн вашего фреймворка, чтобы делать то, что хотите. Единственная функциональность, которую ваш класс TomAction действительно предлагает за пределами AbstractAction, - это обработка ошибок и возможность переименования метода actionPerformed за (высокую, IMHO) стоимость использования отражения и потери безопасности типа.

Я бы реализовал это так:

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

Это немного более многословно, чем ваша существующая реализация, но обеспечивает необходимую безопасность типов во время компиляции.

2 голосов
/ 09 апреля 2010

Вы можете использовать аннотацию вместо строкового литерала, чтобы обозначить вызванное действие, а затем использовать 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
    }

}
2 голосов
/ 09 апреля 2010

Я не знаю, как это сделать с любым существующим программным обеспечением.

Однако, предполагая, что имя метода может быть разрешено во время компиляции (в этом случае маловероятно, что вы использовали бы отражение), вы могли бы создать плагин IDE для этого.

0 голосов
/ 12 апреля 2010

Я думаю, вам просто нужно объявить интерфейс обратного вызова, чтобы дополнить ваш класс 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, чтобы найти первый аргумент для проверки существования метода с соответствующим именем. .

0 голосов
/ 10 апреля 2010

Используйте "константы"

Компилятор не будет жаловаться при передаче строкового литерала, если он имеет правильный тип.

Однако при передаче переменной в качестве параметра переменная должна быть по крайней мере определена.

Вы можете определить некоторую переменную класса "константы" для каждого действия и предложить вызывающей стороне использовать их. Компилятор выдаст вам ошибку, если параметр переменной не определен.

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 голосов
/ 09 апреля 2010

Это возможно с отражением:

        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();
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...