Java 中文官方教程 2022 版(三)(2)https://developer.aliyun.com/article/1486282
局部类和匿名类
有两种额外的内部类。您可以在方法体内声明一个内部类。这些类被称为局部类。您还可以在方法体内声明一个没有命名的内部类。这些类被称为匿名类。
修饰符
您可以为内部类使用与外部类的其他成员相同的修饰符。例如,您可以使用访问修饰符private
、public
和protected
来限制对内部类的访问,就像您用它们来限制对其他类成员的访问一样。
本地类
原文:
docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html
本地类是在块中定义的类,块是在平衡大括号之间的零个或多个语句组成的组。通常在方法体中定义本地类。
本节涵盖以下主题:
- 声明本地类
- 访问封闭类的成员
- 遮蔽和本地类
- 本地类类似于内部类
声明本地类
您可以在任何块中定义本地类(请参阅表达式、语句和块了解更多信息)。例如,您可以在方法体、for
循环或if
子句中定义本地类。
以下示例,LocalClassExample
,验证两个电话号码。它在方法validatePhoneNumber
中定义了本地类PhoneNumber
:
public class LocalClassExample { static String regularExpression = "[⁰-9]"; public static void validatePhoneNumber( String phoneNumber1, String phoneNumber2) { final int numberLength = 10; // Valid in JDK 8 and later: // int numberLength = 10; class PhoneNumber { String formattedPhoneNumber = null; PhoneNumber(String phoneNumber){ // numberLength = 7; String currentNumber = phoneNumber.replaceAll( regularExpression, ""); if (currentNumber.length() == numberLength) formattedPhoneNumber = currentNumber; else formattedPhoneNumber = null; } public String getNumber() { return formattedPhoneNumber; } // Valid in JDK 8 and later: // public void printOriginalNumbers() { // System.out.println("Original numbers are " + phoneNumber1 + // " and " + phoneNumber2); // } } PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1); PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2); // Valid in JDK 8 and later: // myNumber1.printOriginalNumbers(); if (myNumber1.getNumber() == null) System.out.println("First number is invalid"); else System.out.println("First number is " + myNumber1.getNumber()); if (myNumber2.getNumber() == null) System.out.println("Second number is invalid"); else System.out.println("Second number is " + myNumber2.getNumber()); } public static void main(String... args) { validatePhoneNumber("123-456-7890", "456-7890"); } }
该示例通过首先从电话号码中删除除数字 0 到 9 之外的所有字符来验证电话号码。然后,它检查电话号码是否恰好包含十个数字(北美电话号码的长度)。该示例打印如下内容:
First number is 1234567890 Second number is invalid
访问封闭类的成员
本地类可以访问其封闭类的成员。在前面的示例中,PhoneNumber
构造函数访问成员LocalClassExample.regularExpression
。
此外,本地类可以访问局部变量。但是,本地类只能访问声明为 final 的局部变量。当本地类访问封闭块的局部变量或参数时,它会捕获该变量或参数。例如,PhoneNumber
构造函数可以访问局部变量numberLength
,因为它声明为 final;numberLength
是一个捕获的变量。
然而,从 Java SE 8 开始,本地类可以访问封闭块中的局部变量和参数,这些变量是 final 或有效地 final。一旦初始化后值不会改变的变量或参数是有效地 final。例如,假设变量numberLength
没有声明为 final,并且您在PhoneNumber
构造函数中添加了突出显示的赋值语句以将有效电话号码的长度更改为 7 位:
PhoneNumber(String phoneNumber) { numberLength = 7; String currentNumber = phoneNumber.replaceAll( regularExpression, ""); if (currentNumber.length() == numberLength) formattedPhoneNumber = currentNumber; else formattedPhoneNumber = null; }
由于这个赋值语句,变量numberLength
不再是有效地 final。因此,当内部类PhoneNumber
尝试访问numberLength
变量时,Java 编译器生成类似于"从内部类引用的局部变量必须是 final 或有效地 final"的错误消息:
if (currentNumber.length() == numberLength)
从 Java SE 8 开始,如果你在方法中声明局部类,它可以访问方法的参数。例如,你可以在PhoneNumber
局部类中定义以下方法:
public void printOriginalNumbers() { System.out.println("Original numbers are " + phoneNumber1 + " and " + phoneNumber2); }
方法printOriginalNumbers
访问方法validatePhoneNumber
的参数phoneNumber1
和phoneNumber2
。
遮蔽和局部类
在局部类中声明的类型(如变量)会遮蔽在外部作用域中具有相同名称的声明。更多信息请参见 Shadowing。
局部类类似于内部类
局部类类似于内部类,因为它们不能定义或声明任何静态成员。在静态方法中的局部类,比如在静态方法validatePhoneNumber
中定义的PhoneNumber
类,只能引用封闭类的静态成员。例如,如果你没有将成员变量regularExpression
定义为静态的,那么 Java 编译器会生成类似“非静态变量regularExpression
无法从静态上下文中引用”的错误。
局部类是非静态的,因为它们可以访问封闭块的实例成员。因此,它们不能包含大多数类型的静态声明。
你不能在块内部声明接口;接口本质上是静态的。例如,以下代码摘录不会编译,因为接口HelloThere
是在方法greetInEnglish
的主体内定义的:
public void greetInEnglish() { interface HelloThere { public void greet(); } class EnglishHelloThere implements HelloThere { public void greet() { System.out.println("Hello " + name); } } HelloThere myGreeting = new EnglishHelloThere(); myGreeting.greet(); }
你不能在局部类中声明静态初始化程序或成员接口。以下代码摘录不会编译,因为方法EnglishGoodbye.sayGoodbye
被声明为static
。当编译器遇到这个方法定义时,会生成类似“修饰符’static’仅允许在常量变量声明中使用”的错误:
public void sayGoodbyeInEnglish() { class EnglishGoodbye { public static void sayGoodbye() { System.out.println("Bye bye"); } } EnglishGoodbye.sayGoodbye(); }
局部类可以拥有静态成员,前提是它们是常量变量。(常量变量是指声明为 final 并用编译时常量表达式初始化的原始类型或String
类型的变量。编译时常量表达式通常是一个可以在编译时评估的字符串或算术表达式。更多信息请参见理解类成员。)以下代码摘录可以编译,因为静态成员EnglishGoodbye.farewell
是一个常量变量:
public void sayGoodbyeInEnglish() { class EnglishGoodbye { public static final String farewell = "Bye bye"; public void sayGoodbye() { System.out.println(farewell); } } EnglishGoodbye myEnglishGoodbye = new EnglishGoodbye(); myEnglishGoodbye.sayGoodbye(); }
匿名类
原文:
docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html
匿名类使您的代码更加简洁。它们使您能够同时声明和实例化一个类。它们类似于本地类,只是没有名称。如果您只需要使用本地类一次,请使用它们。
本节涵盖以下主题:
- 声明匿名类
- 匿名类的语法
- 访问封闭范围的本地变量,并声明和访问匿名类的成员
- 匿名类示例
声明匿名类
虽然本地类是类声明,匿名类是表达式,这意味着你在另一个表达式中定义类。以下示例,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
对象的语句的一部分。(这就解释了为什么在右括号后有一个分号。)
访问封闭范围的本地变量,并声明和访问匿名类的成员
像本地类一样,匿名类可以捕获变量;它们对封闭范围的本地变量具有相同的访问权限:
- 匿名类可以访问其封闭类的成员。
- 匿名类无法访问其封闭范围中未声明为
final
或有效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
类型的对象。EventHandler
接口只包含一个方法,即 handle。该示例使用匿名类表达式而不是使用新类来实现此方法。请注意,此表达式是传递给btn.setOnAction
方法的参数。
因为EventHandler
接口只包含一个方法,所以您可以使用 lambda 表达式代替匿名类表达式。有关更多信息,请参阅 Lambda 表达式部分。
匿名类非常适合实现包含两个或更多方法的接口。以下 JavaFX 示例来自自定义 UI 控件部分。突出显示的代码创建一个仅接受数字值的文本字段。它通过覆盖从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); } }
Lambda 表达式
原文:
docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
匿名类的一个问题是,如果您的匿名类的实现非常简单,例如只包含一个方法的接口,那么匿名类的语法可能显得笨拙和不清晰。在这些情况下,通常您试图将功能作为参数传递给另一个方法,例如当某人单击按钮时应执行什么操作。Lambda 表达式使您能够做到这一点,将功能视为方法参数,或将代码视为数据。
前一节,匿名类,向您展示了如何实现一个没有名称的基类。尽管这通常比具有名称的类更简洁,但对于只有一个方法的类来说,即使是匿名类似乎也有点过多和繁琐。Lambda 表达式让您更简洁地表达单方法类的实例。
本节涵盖以下主题:
- Lambda 表达式的理想使用情况
- 方法 1:创建搜索符合一个特征的成员的方法
- 方法 2:创建更通用的搜索方法
- 方法 3:在本地类中指定搜索条件代码
- 方法 4:在匿名类中指定搜索条件代码
- 方法 5:使用 Lambda 表达式指定搜索条件代码
- 方法 6:使用 Lambda 表达式与标准功能接口
- 方法 7:在整个应用程序中使用 Lambda 表达式
- 方法 8:更广泛地使用泛型
- 方法 9:使用接受 Lambda 表达式作为参数的聚合操作
- GUI 应用程序中的 Lambda 表达式
- Lambda 表达式的语法
- 访问封闭范围的局部变量
- 目标类型
- 目标类型和方法参数
- 序列化
Lambda 表达式的理想使用情况
假设您正在创建一个社交网络应用程序。您希望创建一个功能,使管理员能够对满足特定条件的社交网络应用程序成员执行任何类型的操作,例如发送消息。以下表格详细描述了这种用例:
字段 | 描述 |
名称 | 对所选成员执行操作 |
主要执行者 | 管理员 |
前提条件 | 管理员已登录到系统。 |
后置条件 | 操作仅在符合指定条件的成员上执行。 |
主要成功场景 |
- 管理员指定要执行某个操作的成员的条件。
- 管理员指定对所选成员执行的操作。
- 管理员选择提交按钮。
- 系统找到所有符合指定条件的成员。
- 系统对所有匹配成员执行指定操作。
|
扩展 | 1a. 管理员在指定执行操作或选择提交按钮之前有选项预览符合指定条件的成员。 |
出现频率 | 一天中多次。 |
假设这个社交网络应用程序的成员由以下Person
类表示:
public class Person { public enum Sex { MALE, FEMALE } String name; LocalDate birthday; Sex gender; String emailAddress; public int getAge() { // ... } public void printPerson() { // ... } }
假设您的社交网络应用程序的成员存储在List
实例中。
本节从一个简单的方法开始处理这种用例。它通过本地和匿名类改进了这种方法,然后以使用 lambda 表达式的高效简洁方法结束。在示例RosterTest
中找到本节描述的代码摘录。
方法 1:创建搜索符合一个特征的成员的方法
一个简单的方法是创建几种方法;每种方法搜索符合一个特征的成员,例如性别或年龄。以下方法打印比指定年龄更老的成员:
public static void printPersonsOlderThan(List<Person> roster, int age) { for (Person p : roster) { if (p.getAge() >= age) { p.printPerson(); } } }
注意:List
是一个有序的Collection
。集合是将多个元素组合成单个单元的对象。集合用于存储、检索、操作和传递聚合数据。有关集合的更多信息,请参阅 Collections 教程。
这种方法可能会使您的应用程序变得脆弱,这是应用程序由于引入更新(如新数据类型)而无法工作的可能性。假设您升级了应用程序并更改了Person
类的结构,使其包含不同的成员变量;也许该类使用不同的数据类型或算法记录和测量年龄。您将不得不重写大量 API 以适应这种变化。此外,这种方法是不必要地限制性的;例如,如果您想打印比某个年龄更年轻的成员会怎样?
Java 中文官方教程 2022 版(三)(4)https://developer.aliyun.com/article/1486284