Contents

Java的匿名类简介

1. 简介

在本教程中,我们将考虑 Java 中的匿名类。 我们将描述如何声明和创建它们的实例。我们还将简要讨论它们的属性和限制。

2. 匿名类声明

**匿名类是没有名字的内部类。**由于它们没有名称,我们不能使用它们来创建匿名类的实例。因此,我们必须在使用时在单个表达式中声明和实例化匿名类。 我们可以扩展现有的类或实现接口。

2.1. 扩展一个类

当我们从一个存在的类实例化一个匿名类时,我们使用以下语法:

/uploads/java_anonymous_classes/1.png

在括号中,我们指定了我们正在扩展的类的构造函数所需的参数:

new Book("Design Patterns") {
    @Override
    public String description() {
        return "Famous GoF book.";
    }
}

当然,如果父类构造函数不接受任何参数,我们应该将括号留空。

2.2. 实现一个接口

我们也可以从一个接口实例化一个匿名类:

/uploads/java_anonymous_classes/3.png

显然,Java 的接口没有构造函数,因此括号始终为空。这是我们实现接口方法的唯一方法:

new Runnable() {
    @Override
    public void run() {
        ...
    }
}

一旦我们实例化了一个匿名类,我们就可以将该实例分配给一个变量,以便以后能够在某个地方引用它。

我们可以使用 Java 表达式的标准语法来做到这一点:

Runnable action = new Runnable() {
    @Override
    public void run() {
        ...
    }
};

正如我们已经提到的,匿名类声明是一个表达式,因此它必须是语句的一部分。这就解释了为什么我们在语句的末尾放了一个分号。

显然,如果我们内联创建该实例,我们可以避免将实例分配给变量:

List<Runnable> actions = new ArrayList<Runnable>();
actions.add(new Runnable() {
    @Override
    public void run() {
        ...
    }
});

我们应该非常小心地使用这种语法,因为它可能很容易影响代码的可读性,尤其是当*run()*方法的实现占用大量空间时。

3. 匿名类属性

相对于通常的顶级类,使用匿名类有一些特殊性。在这里,我们简要地触及最实际的问题。对于最准确和最新的信息,我们可能总是查看Java 语言规范

3.1. 构造函数

匿名类的语法不允许我们让它们实现多个接口。在构造过程中,可能只存在一个匿名类的实例。因此,它们永远不可能是抽象的。因为它们没有名字,我们不能扩展它们。出于同样的原因,匿名类不能有显式声明的构造函数。

事实上,没有构造函数对我们来说并不代表任何问题,原因如下:

  1. 我们在声明它们的同时创建匿名类实例
  2. 从匿名类实例中,我们可以访问局部变量和封闭类的成员

3.2. 静态成员

匿名类不能有任何静态成员,除了那些是常量的。 例如,这不会编译:

new Runnable() {
    static final int x = 0;
    static int y = 0; // compilation error!
    @Override
    public void run() {...}
};

相反,我们会收到以下错误:

The field y cannot be declared static in a non-static inner type, unless initialized with a constant expression

3.3. 变量范围

匿名类捕获在我们声明类的块范围内的局部变量:

int count = 1;
Runnable action = new Runnable() {
    @Override
    public void run() {
        System.out.println("Runnable with captured variables: " + count);
    }
};

如我们所见,局部变量 countaction定义在同一个块中。因此,我们可以从类声明中访问count

**请注意,为了能够使用局部变量,它们必须是有效的最终变量。**从 JDK 8 开始,我们不再需要使用关键字final声明变量。然而,这些变量必须是final的。否则,我们会得到一个编译错误:

[ERROR] local variables referenced from an inner class must be final or effectively final

为了让编译器确定一个变量实际上是不可变的,在代码中,我们应该只在一个地方给它赋值。我们可能会在我们的文章“为什么 Lambdas 中使用的局部变量必须是最终变量或有效最终变量? ” 让我们提一下,作为每个内部类,匿名类可以访问其封闭类的所有成员

4. 匿名类用例

匿名类的应用可能有很多种。让我们探索一些可能的用例。

4.1. 类层次结构和封装

我们应该在一般用例中使用内部类,在非常具体的用例中使用匿名类,以便在我们的应用程序中实现更清晰的类层次结构。当使用内部类时,我们可以对封闭类的数据进行更精细的封装。如果我们在顶级类中定义内部类功能,那么封闭类应该对其某些成员具有公共或*包可见性。*当然,在某些情况下,它不是很欣赏甚至不被接受。

4.2. 更清洁的项目结构

当我们必须动态修改某些类的方法的实现时,我们通常使用匿名。在这种情况下,我们可以避免将新的 *.java 文件添加到项目中以定义顶级类。如果仅使用一次顶级类,则尤其如此。

4.3. UI 事件监听器

在具有图形界面的应用程序中,匿名类最常见的用例是创建各种事件侦听器。例如,在以下代码段中:

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        ...
    }
}

我们创建一个实现接口ActionListener的匿名类的实例。当用户点击按钮时,它的 actionPerformed方法被触发。

从 Java 8 开始,lambda 表达式 似乎是一种更受欢迎的方式。

5. 一般情况

我们上面考虑的匿名类只是嵌套类 的一个特例。通常,嵌套类是在另一个类或接口中声明的类

/uploads/java_anonymous_classes/5.png

查看图表,我们看到匿名类与*local nonstatic 成员类一起形成了所谓的inner 类。它们与static *类一起构成嵌套类。