Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
匿名类使你可以使代码更简洁。它们使你能够同时声明和实例化一个类。它们就像局部类,除了它们没有名称。如果你只需要使用局部类一次,请使用它们。
本节包括以下主题:
虽然局部类是类声明,但匿名类是表达式,这意味着你在另一个表达式中定义该类。以下示例 HelloWorldAnonymousClasses
在局部变量 frenchGreeting
和 spanishGreeting
的初始化语句中使用匿名类,但是使用局部类初始化变量 englishGreeting
:
public class HelloWorldAnonymousClasses { interface HelloWorld { public void greet(); public void greetSomeone(String someone); } public void sayHello() { class EnglishGreeting implements HelloWorld { String name = "world"; public void greet() { greetSomeone("world"); } public void greetSomeone(String someone) { name = someone; System.out.println("Hello " + name); } } HelloWorld englishGreeting = new EnglishGreeting(); HelloWorld frenchGreeting = new HelloWorld() { String name = "tout le monde"; public void greet() { greetSomeone("tout le monde"); } public void greetSomeone(String someone) { name = someone; System.out.println("Salut " + name); } }; HelloWorld spanishGreeting = new HelloWorld() { String name = "mundo"; public void greet() { greetSomeone("mundo"); } public void greetSomeone(String someone) { name = someone; System.out.println("Hola, " + name); } }; englishGreeting.greet(); frenchGreeting.greetSomeone("Fred"); spanishGreeting.greet(); } public static void main(String... args) { HelloWorldAnonymousClasses myApp = new HelloWorldAnonymousClasses(); myApp.sayHello(); } }
如前所述,匿名类是一个表达式。匿名类表达式的语法类似于构造函数的调用,只是在代码块中包含类定义。
考虑 frenchGreeting
对象的实例化:
HelloWorld frenchGreeting = new HelloWorld() { String name = "tout le monde"; public void greet() { greetSomeone("tout le monde"); } public void greetSomeone(String someone) { name = someone; System.out.println("Salut " + name); } };
匿名类表达式包含以下内容:
new
运算符
要实现的接口的名称或要继承的类。在此示例中,匿名类实现接口 HelloWorld
。
包含构造函数参数的圆括号,就像普通的类实例创建表达式一样。注意:实现接口时,没有构造函数,因此你使用一对空括号,如本例所示。
类主体,是一个类声明体。更具体地说,在主体中,方法申明是允许的,但语句不允许。
因为匿名类定义是表达式,所以它必须是语句的一部分。在此示例中,匿名类表达式是实例化 frenchGreeting
对象的语句的一部分。(这解释了为什么在右花括号后边有一个分号。)
像局部类一样,匿名类可以 capture variables (捕获变量);它们对封闭范围的局部变量具有相同的访问权限:
匿名类可以访问其封闭类的成员。
匿名类无法访问其封闭范围中未声明为 final
或 effectively final 的局部变量。
与嵌套类一样,匿名类中的类型(例如变量)的声明,会遮蔽封闭范围中具有相同名称的任何其他声明。有关更多信息,请参见 遮蔽。
匿名类对其成员也具有与局部类相同的限制:
你不能在匿名类中声明静态初始化程序或成员接口。
匿名类可以具有静态成员,前提是它们是常量变量。
请注意,你可以在匿名类中声明以下内容:
字段
额外的方法(即使他们没有实现超类型的任何方法)
实例初始化器
局部类
但是,你不能在匿名类中声明构造函数。
匿名类通常用于图形用户界面(GUI)应用程序。
考虑 JavaFX 示例 HelloWorld.java
(来自 Hello World, JavaFX Style Getting Started with JavaFX) 。此示例创建一个包含 Say 'Hello World' 按钮的框架。匿名类表达式高亮:
import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class HelloWorld extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("Hello World!"); Button btn = new Button(); btn.setText("Say 'Hello World'"); btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); } }); StackPane root = new StackPane(); root.getChildren().add(btn); primaryStage.setScene(new Scene(root, 300, 250)); primaryStage.show(); } }
在此示例中,方法调用 btn.setOnAction
指定当你选择 Say 'Hello World' 按钮时会发生什么。此方法需要类型为 EventHandler<ActionEvent>
的对象。EventHandler<ActionEvent>
接口只包含一个方法 handle。该示例使用匿名类表达式,而不是使用新类实现此方法。请注意,此表达式是传递给 btn.setOnAction
方法的参数。
因为 EventHandler<ActionEvent>
接口只包含一个方法,所以可以使用 lambda 表达式而不是匿名类表达式。有关更多信息,请参阅 Lambda Expressions 部分。
匿名类是实现包含两个或更多方法的接口的理想选择。以下 JavaFX 示例来自 Customization of UI Controls 部分。高亮的代码创建一个只接受数值的文本框。它通过使用匿名类重写从 TextInputControl
继承的 replaceText
和 replaceSelection
方法,重新定义了TextField
类的默认实现。
import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.stage.Stage; public class CustomTextFieldSample extends Application { final static Label label = new Label(); @Override public void start(Stage stage) { Group root = new Group(); Scene scene = new Scene(root, 300, 150); stage.setScene(scene); stage.setTitle("Text Field Sample"); GridPane grid = new GridPane(); grid.setPadding(new Insets(10, 10, 10, 10)); grid.setVgap(5); grid.setHgap(5); scene.setRoot(grid); final Label dollar = new Label("$"); GridPane.setConstraints(dollar, 0, 0); grid.getChildren().add(dollar); final TextField sum = new TextField() { @Override public void replaceText(int start, int end, String text) { if (!text.matches("[a-z, A-Z]")) { super.replaceText(start, end, text); } label.setText("Enter a numeric value"); } @Override public void replaceSelection(String text) { if (!text.matches("[a-z, A-Z]")) { super.replaceSelection(text); } } }; sum.setPromptText("Enter the total"); sum.setPrefColumnCount(10); GridPane.setConstraints(sum, 1, 0); grid.getChildren().add(sum); Button submit = new Button("Submit"); GridPane.setConstraints(submit, 2, 0); grid.getChildren().add(submit); submit.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { label.setText(null); } }); GridPane.setConstraints(label, 0, 1); GridPane.setColumnSpan(label, 3); grid.getChildren().add(label); scene.setRoot(grid); stage.show(); } public static void main(String[] args) { launch(args); } }