Anonymous Classes in Java – Java中的匿名类

最后修改: 2019年 4月 19日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’ll consider anonymous classes in Java.

在本教程中,我们将考虑Java中的匿名类。

We’ll describe how we can declare and create instances of them. We’ll also briefly discuss their properties and limitations.

我们将描述我们如何声明和创建它们的实例。我们还将简要地讨论它们的属性和限制。

2. Anonymous Class Declaration

2.匿名类声明

Anonymous classes are inner classes with no name. Since they have no name, we can’t use them in order to create instances of anonymous classes. As a result, we have to declare and instantiate anonymous classes in a single expression at the point of use.

匿名类是没有名字的内部类。由于它们没有名字,我们不能使用它们来创建匿名类的实例。因此,我们必须在使用点上用一个表达式来声明和实例化匿名类。

We may either extend an existing class or implement an interface.

我们可以扩展一个现有的类或实现一个接口。

2.1. Extend a Class

2.1.扩展一个类

When we instantiate an anonymous class from an existent one, we use the following syntax:

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

AnonymousClass InstantiateFromClass

In the parentheses, we specify the parameters that are required by the constructor of the class that we are extending:

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

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

Naturally, if the parent class constructor accepts no arguments, we should leave the parentheses empty.

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

2.2. Implement an Interface

2.2.实现一个接口

We may instantiate an anonymous class from an interface as well:

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

AnonymousClass InstantiateFromInterface

Obviously, Java’s interfaces have no constructors, so the parentheses always remain empty. This is the only way we should do it to implement the interface’s methods:

很明显,Java的接口没有构造函数,所以括号里永远是空的。为了实现接口的方法,我们应该这样做。

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

Once we have instantiated an anonymous class, we can assign that instance to a variable in order to be able to reference it somewhere later.

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

We can do this using the standard syntax for Java expressions:

我们可以使用Java表达式的标准语法来做这件事。

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

As we already mentioned, an anonymous class declaration is an expression, hence it must be a part of a statement. This explains why we have put a semicolon at the end of the statement.

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

Obviously, we can avoid assigning the instance to a variable if we create that instance inline:

显然,如果我们在内联中创建实例,就可以避免将该实例分配给一个变量。

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

We should use this syntax with great care as it might easily suffer the code readability especially when the implementation of the run() method takes a lot of space.

我们应该非常谨慎地使用这种语法,因为它可能很容易影响代码的可读性,特别是当run()方法的实现需要大量的空间时。

3. Anonymous Class Properties

3.匿名类属性

There are certain particularities in using anonymous classes with respect to usual top-level classes. Here we briefly touch the most practical issues. For the most precise and updated information, we may always look at the Java Language Specification.

相对于通常的顶层类而言,使用匿名类有一定的特殊性。在这里,我们简要地谈一下最实际的问题。对于最精确和最新的信息,我们可以随时查看《Java语言规范》

3.1. Constructor

3.1. 建筑商

The syntax of anonymous classes does not allow us to make them implement multiple interfaces. During construction, there might exist exactly one instance of an anonymous class. Therefore, they can never be abstract. Since they have no name, we can’t extend them. For the same reason, anonymous classes cannot have explicitly declared constructors.

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

In fact, the absence of a constructor doesn’t represent any problem for us for the following reasons:

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

  1. we create anonymous class instances at the same moment as we declare them
  2. from anonymous class instances, we can access local variables and enclosing class’s members

3.2. Static Members

3.2.静态成员

Anonymous classes cannot have any static members except for those that are constant.

除了那些常量成员,匿名类不能有任何静态成员。

For example, this won’t compile:

例如,这将不会被编译。

new Runnable() {
    static final int x = 0;
    static int y = 0; // compilation error!

    @Override
    public void run() {...}
};

Instead, we’ll get the following error:

相反,我们会得到以下错误。

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

3.3. Scope of Variables

3.3.变量的范围

Anonymous classes capture local variables that are in the scope of the block in which we have declared the class:

匿名类捕获局部变量,这些变量在我们声明该类的块的范围内。

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

As we see, the local variables count and action are defined in the same block. For this reason, we can access count from within the class declaration.

正如我们看到的,局部变量countaction被定义在同一个块中。出于这个原因,我们可以在类声明中访问count

Note that in order to be able to use local variables, they must be effectively final. Since JDK 8, it is not required anymore that we declare variables with the keyword final. Nevertheless, those variables must be final. Otherwise, we get a compilation error:

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

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

In order the compiler decides that a variable is, in fact, immutable, in the code, there should be only one place in which we assign a value to it. We might find more information about effectively final variables in our article “Why Do Local Variables Used in Lambdas Have to Be Final or Effectively Final?

为了让编译器确定一个变量事实上是不可改变的,在代码中,应该只有一个地方我们给它赋值。我们可以在我们的文章”为什么在Lambdas中使用的局部变量必须是最终变量或有效最终变量“中找到更多关于有效最终变量的信息。

Let us just mention that as every inner class, an anonymous class can access all members of its enclosing class.

让我们提一下,就像每个内层类一样,匿名类可以访问其包围类的所有成员

4. Anonymous Class Use Cases

4.匿名类用例

There might be a big variety of applications of anonymous classes. Let’s explore some possible use cases.

匿名类的应用可能有很大的多样性。让我们来探讨一些可能的用例。

4.1. Class Hierarchy and Encapsulation

4.1.类的层次结构和封装

We should use inner classes in general use cases and anonymous ones in very specific ones in order to achieve a cleaner hierarchy of classes in our application. When using inner classes, we may achieve a finer encapsulation of the enclosing class’s data. If we define the inner class functionality in a top-level class, then the enclosing class should have public or package visibility of some of its members. Naturally, there are situations when it is not very appreciated or even accepted.

我们应该在一般的用例中使用内层类,在非常特殊的用例中使用匿名类,以便在我们的应用程序中实现更清晰的类的层次结构。当使用内层类时,我们可以实现对包围类的数据进行更精细的封装。如果我们在一个顶层类中定义了内层类的功能,那么包围类的一些成员应该具有publicpackage的可见性。自然,在有些情况下,这是不太被欣赏甚至接受的。

4.2. Cleaner Project Structure

4.2.清洁的项目结构

We usually use anonymous classes when we have to modify on the fly the implementation of methods of some classes. In this case, we can avoid adding new *.java files to the project in order to define top-level classes. This is especially true if that top-level class would be used just one time.

当我们必须修改某些类的方法的on the fly实现时,我们通常使用匿名类。在这种情况下,我们可以避免在项目中添加新的*.java文件,以定义顶级类。如果顶层类只被使用一次,这一点尤其正确。

4.3. UI Event Listeners

4.3.UI事件监听器

In applications with a graphical interface, the most common use case of anonymous classes is to create various event listeners. For example, in the following snippet:

在具有图形界面的应用程序中,匿名类最常见的使用情况是创建各种事件监听器。例如,在下面的片段中。

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

we create an instance of an anonymous class that implements interface ActionListener. Its actionPerformed method gets triggered when a user clicks the button.

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

Since Java 8, lambda expressions seem to be a more preferred way though.

自Java 8以来,lambda表达式似乎是一种更受欢迎的方式。

5. General Picture

5.一般图片

Anonymous classes that we considered above are just a particular case of nested classes. Generally, a nested class is a class that is declared inside another class or interface:

我们上面考虑的匿名类只是嵌套类的一个特殊情况。一般来说,嵌套类是一个在另一个类或接口内声明的类

nested classes

Looking at the diagram, we see that anonymous classes along with local and nonstatic member ones form the so-called inner classes. Together with static member classes, they form the nested classes.

观察该图,我们看到匿名类与本地非静态成员构成了所谓的内部类。与静态成员类一起,它们形成了嵌套类。

6. Conclusion

6.结语

In this article, we’ve considered various aspects of Java anonymous classes. We’ve described as well a general hierarchy of nested classes.

在这篇文章中,我们已经考虑了Java匿名类的各个方面。我们也描述了嵌套类的一般层次结构。

As always, the complete code is available over in our GitHub repository.

一如既往,完整的代码可在我们的GitHub资源库中找到。