Java内部类的使用和实例化

9

有一段时间前,我用Java编写了一个小型图像浏览器/处理程序,类似于迷你版Photoshop。

我想要一个下拉菜单,可以从我打开的图像中选择要“放在桌面上”的那个,即显示并应用方法。我希望图像的名称是菜单中JMenuItem的名称。同时,当我添加新图像时,我也希望出现一个新按钮。

我思考了很长时间,最终产生了这个解决方案,一个处理新按钮创建的新类,代码如下:

import java.awt.event.*;
import javax.swing.*;
import java.util.*;


public class ImageList{

    private ArrayList<JMenuItem> list;
    private ImageHandler main;
    private ImageLevel img;

    public ImageList() {}

    public void setHandler(ImageHandler hand) {
        main = hand;
        img = main.getImg1();
    }

    public void add(Buffer addi) {
        final String added = addi.getName();
        JMenuItem uusi = new JMenuItem(added);

        main.getMenu5().add(uusi);
        uusi.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                img.setBuffer(added);
                main.getScr().updateUI();
            }
        });
   }
}

这个功能正常工作。对于这个网站,我将原来的芬兰语名称翻译成了英语,不知道为什么我最初用芬兰语写它们...我很擅长给事物命名。

方法add应该在程序运行时被多次调用。

我真正无法理解的是接口ActionListener的内部类实现,即其编译和工作原理。

如果我的界面上有两个按钮,我希望它们做不同的事情,我需要两个内部类,每个内部类都有自己的接口ActionListener的内部实现。但是在我的代码中,有一个类似于许多工作的类,只有一个已编译的.class文件,但最终结果的工作方式就好像有很多个。

有人能教我这个问题吗?这段代码是一个类,新按钮是它的实例吗?它们是新类吗?每个新按钮都应该有一个新的.class文件吗?等等...


3
关于命名:new是一个保留字,因此您应该为JMenuItem变量使用其他名称。 - assylias
还要考虑“Action”,如[FileMenu](https://dev59.com/YW855IYBdhLWcg3w_5qm#4039359)中所示。 - trashgod
6个回答

5

通常,内部类在仅调用一次的代码中实例化(例如,在扩展JPanel并在构造函数中向JButton添加ActionListeners时)。在这里,您在一个方法中实例化了一个内部类,该方法被多次调用(如果我正确理解您的描述)。每次调用add()时,都会创建内部类的新实例。与命名类一样,只有一个类,但可以有许多实例。


@Code-Guru:好的,所以它们是新实例。我从来没有见过,或者至少没有注意到,有多个实例的内部类。 - Valtteri
@Valtteri 没错。请参考 Ted Hopp 的回答 ,了解如何使用命名的内部类完成同样的操作。就创建实例而言,这两种方法是等效的。 - Code-Apprentice

3
在这段代码中:
public void add(Buffer addi) {
    . . .
    uusi.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            img.setBuffer(added);
            main.getScr().updateUI();
        }
    });
}

new ActionListener() {...} 构造函数是一个匿名内部类。它的行为就像它是作为一个普通内部类单独声明的一样。主要区别在于名称是由编译器自动生成的。它等同于:

private class Anonymous implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        img.setBuffer(added);
        main.getScr().updateUI();
    }
}

public void add(Buffer addi) {
    . . .
    uusi.addActionListener(new Anonymous());
}

每次执行addActionListener代码时,它都会创建此类的新实例。
匿名内部类有一些其他限制,这是匿名的结果。例如,如果它们声明字段,则只能在类内部(不使用反射)访问这些字段。

两者之间的区别在于匿名内部类可以访问其所包含上下文(即added)中的 final 局部变量,但是您编写的 Anonymous 类不能访问(因为它在方法外)。您可以将 private class Anonymous 移动到 add 方法内部,使其与 OP 的代码等效。 - Ian Roberts
@mKorbel:是的,我只是到处添加了那一行代码,希望它能解决一些问题,但似乎并没有造成任何损害...将来会注意这个问题。 - Valtteri
@IanRoberts - 关于final本地变量的观点非常好。我漏掉了这个。 - Ted Hopp

0

你正在为菜单项动作监听器创建新的匿名类。

基本上,你的代码

menuItem.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
      img.setBuffer(added);
      main.getScr().updateUI();
  }
});

为ActionListener定义一个新的类实现。


2
这不是正确的...匿名类是在编译时创建而不是运行时。 - Renato

0
你正在创建一个 ActionListener 接口的匿名实现。每个匿名实现都将有自己的类文件(称为 ImageList$1.class,ImageList$2.class 等)。
你也可以像这样做,获得类似的结果:
class MyActionListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      img.setBuffer(added);
      main.getScr().updateUI();
    }
};

menuItem.addActionListener(new MyActionListener());

0
  • main.getScr().updateUI(); 不是更新已经可见的Swing对象的正确方法,该方法与外观相关。

  • 使用JPanel(不要忘记覆盖PreferredSize,否则返回Dimension(0,0))和paintComponent

  • 如果要删除并添加一个包含图像的新容器,则必须调用revalidate()repaint()


  • 使用带有图像的JList而不是JMenuItem

  • ListSelectionListener添加到JList,然后您可以在图像之间切换


  • 如果没有添加其他JComponent(s)到包含ImageContainer中,则可以使用带有Icon / ImageIconJLabel

基础的东西,只是为了避免在此描述盲目操作。 - mKorbel
@mKorbel 是的,我知道有人会用这些建议来攻击我 :D 无论如何感谢你。我仍然不太理解GUI和一些基本概念,所以这段代码非常零散而且勉强能运行,真是个奇迹... 无论如何我会仔细阅读你的建议。 - Valtteri
因为这里的一切都与动态添加组件到JDialog有关。 - mKorbel

0

类内部的内部类编译成“父” .class 文件。 您可以在一个类内部拥有多个内部类(编译后会生成1个.class文件)。 每次对内部类进行“调用”(运行时),将创建该内部类的对象(与“普通类”相同)。但它不会更改您的 .class 文件。

因此,如果添加了3个相同类型的按钮,则每次都会创建该内部类的新对象。与按钮本身的工作原理相同。


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