实现ActionListener的Java匿名类是什么?

5

最近我在做一个编程作业,需要根据UML图示在代码中实现一个程序。有一部分指定我需要创建一个匿名的JButton,这个按钮显示一个从1开始的计数器,并且每次点击后递减。JButton和它的ActionListener都必须是匿名的。

我想到了以下解决方案:

public static void main(String[] args) {
  JFrame f = new JFrame("frame");
  f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  f.setSize(400, 400);

  f.getContentPane().add(new JButton() {

    public int counter;

    {
      this.counter = 1;
      this.setBackground(Color.ORANGE);
      this.setText(this.counter + "");

      this.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent arg0) {
          counter --;
          setText(counter + "");
        }
      });

    }
  });

  f.setVisible(true);

}

这将添加一个匿名的JButton,然后添加另一个(内部)匿名ActionListener来处理事件并根据需要更新按钮的文本。是否有更好的解决方案?我相当确定我不能声明一个实现ActionListener的匿名JButton,但是是否有另一种更优雅的方法来实现相同的结果?


看看你现在在哪里,@Tim。 - Insane
6个回答

13

我通常会像这样做:

JPanel panel = new JPanel();
panel.add(new JButton(new AbstractAction("name of button") {
    public void actionPerformed(ActionEvent e) {
        //do stuff here
    }
}));

AbstractAction实现了ActionListener接口,因此应该可以满足任务要求。

将如此多的代码行挤在一起可能是不好的习惯,但如果你习惯于阅读它,则可以非常优雅。


这实际上不允许您在动作侦听器内更改按钮文本...这是相当尴尬的要求,我无法想到任何改进您已拥有的方式。 - Cogsy
那么在匿名JButton中公开声明计数器,并在ActionListener内部对其进行未经限定的访问是可以的吗? - Tim
是的,这是你能够掌握那些字段的唯一方法。只要记住,内部类中的字段将隐藏在外部作用域中声明的同名字段。希望你的编译器/IDE会明确地警告你。 - Cogsy

4

虽然不太美观,但您可以使用ActionListener方法和匿名类来执行以下操作:

  f.getContentPane().add(new JButton(new AbstractAction("name of button") {
      private int counter = 0;

      public void actionPerformed(ActionEvent e) {
          ((JButton) e.getSource()).setText(Integer.toString(counter--));
      }
  }) {
      {
          setText("1");
      }
  });

为了更方便地访问计数器,您可以将其移到类的顶层,并从调用setText的两个地方访问它。

+1。可能会有点“丑”(这是你的话,不是我的),但我觉得比问题中提供的代码更容易理解。我猜唯一的问题就是它是否符合“JButton及其ActionListener都必须是匿名的”这个标准。AbstractAction实现了ActionListener,但是教练可能认为没有满足标准。 - Grant Wagner

2

实现多个类型通常是一个不好的想法。

虽然很多糟糕的软件和教程这样做,但很少需要扩展JComponent类。最近越来越流行的一种习语/技巧是双括号 - 一个类只有在子类中才能给它一个实例初始化器,该初始化器的作用类似于其他语言中的with语句。

在这种情况下,相关代码可以编写为:

JButton button = new JButton();
button.addActionListener(new ActionListener() {
    int counter = 1;
    {
        updateText();
    }
    public void actionPerformed(ActionEvent arg0) {
        --counter;
        updateText();
    }
    private void updateText()
        setText(Integer.toString(counter));
    }
});
f.getContentPane(button);

如果变得更为复杂,那么您可能需要创建一个外部类(不实现 ActionListener 或扩展 JButton)来处理数据。
另外请注意,应该使用 EventQueue.invokeLater 样板代码来确保 Swing 组件仅在 AWT EDT 上使用。

2
updateText不能仅使用setText工作 - 我认为你需要使用button.setText,或将监听器嵌套在JButton的新匿名子类中。此外,f.getContentPane不接受任何参数,我认为你的意思是f.getContentPane.add(button)。 - Kothar

1

有一种更加优雅的方法来实现它。

不幸的是,这不是一个核心Java/Swing的方法。

您可以使用Groovy中的SwingBuilder来实现相同的结果,使用略微更简洁的语法,例如伪代码:

button(text: '' + counter,
         actionPerformed: {counter--; text = '' + counter + ''},
         constraints:BL.SOUTH)

[http://groovy.codehaus.org/Swing+Builder][1]

虽然我看到过学生真的偏离规范并因此被扣分,但我不建议你在作业中使用这个。不过,至少你可以将其作为进一步研究的可能途径。

我认为你现在所拥有的已经完全足够了。


1

在现实世界的编程中,我不会做那样的事情,但考虑到你的任务要求,这已经是最好的选择了。


0

这是一项只在作业中强制执行的不良实践任务 ;-) 不好的地方:

  • 使用 ActionListener 而不是 Action,这本身就是不好的
  • 因此,作用域问题会浮出水面
    • 计数器的范围比必要的范围更广
    • 需要通过类型转换或访问周围对象的 API 来访问 actionPerformed 中的按钮
  • 难以阅读(也就是说:难以维护)的代码

但是,我们无法抗拒,对吧 ;-) 这里有一个使用 Action 的版本,对前两个问题来说很干净(或者我认为是这样),对所有其他示例都难以阅读(而且我当然作弊了:首先实现了匿名类,然后让 IDE 进行内联)

    f.add(new JButton(new AbstractAction() {

        int counter = 1;
        { // constructor block of action
            updateName();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            counter--;
            updateName();
        }

        private void updateName() {
            putValue(Action.NAME, "" + counter);
        }

    })  { // subclass button 
          {  // constructor block button
            setBackground(Color.PINK);
        }}
    );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接