你好!再来说说另一种嵌套类。我说的是本地类(方法本地内部类)。在深入研究之前,我们必须首先记住它们在嵌套类结构中的位置。 从我们的图中可以看出,局部类是内部类的一个亚种,我们在之前的资料中详细讲过。然而,局部类与普通内部类有许多重要的特征和区别。最主要的是在他们的声明中:本地类只在代码块中声明。大多数情况下,此声明位于外部类的某些方法内。 例如,它可能看起来像这样:
public class PhoneNumberValidator {
public void validatePhoneNumber(String number) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
// ...number validation code
}
}
重要的!如果你安装了 Java 7,这段代码在粘贴到 IDEA 时将不会编译。我们将在本课结束时讨论其原因。简而言之,本地类的工作方式高度依赖于语言的版本。如果这段代码没有为你编译,你可以将 IDEA 中的语言版本切换为 Java 8,或者将单词添加final
到方法参数中,使其看起来像这样:validatePhoneNumber(final String number)
。之后,一切都会奏效。这是一个验证电话号码的小程序。它的validatePhoneNumber()
方法以一个字符串作为输入,并判断它是否是一个电话号码。在这个方法中,我们声明了我们的本地PhoneNumber
类。你可能会合理地问为什么。为什么我们要在方法中声明一个类?为什么不使用普通的内部类?没错,我们本可以做到PhoneNumber
类内部类。但最终的解决方案取决于你程序的结构和目的。让我们回忆一下内部类课程中的示例:
public class Bicycle {
private String model;
private int maxWeight;
public Bicycle(String model, int maxWeight) {
this.model = model;
this.maxWeight = maxWeight;
}
public void start() {
System.out.println("Let's go!");
}
public class HandleBar {
public void right() {
System.out.println("Steer right!");
}
public void left() {
System.out.println("Steer left!");
}
}
}
在其中,我们制作了HandleBar
自行车的内部类。有什么不同?首先,类的使用方式不同。第二个示例中的类是比第一个示例中的类HandleBar
更复杂的实体。PhoneNumber
首先,HandleBar
有 publicright
和left
方法(这些不是 setter/getter)。其次,不可能提前预测我们可能需要它的地方和它的外部Bicycle
类。可能有几十个不同的地方和方法,即使在一个程序中也是如此。但是有了PhoneNumber
课堂,一切就简单多了。我们的程序非常简单。它只有一个目的:检查一个号码是否是有效的电话号码。在大多数情况下,我们的PhoneNumberValidator
甚至不会是一个独立的程序,而是更大程序的授权逻辑的一部分。例如,各种网站在用户注册时经常要求提供电话号码。如果你输入一些废话而不是数字,网站会报错:“This is not a phone number!” 此类网站(或更确切地说,其用户授权机制)的开发人员可以包含类似于我们的内容PhoneNumberValidator
在他们的代码中。换句话说,我们有一个外部类和一个方法,它将在程序中的一个地方使用,而不会在其他地方使用。如果使用它,那么它不会有任何改变:一种方法完成它的工作——仅此而已。在这种情况下,因为所有的逻辑都集中在一个方法中,所以在那里封装一个额外的类会更加方便和合适。除了 getter 和 setter 之外,它没有自己的方法。事实上,我们只需要来自构造函数的数据。它不涉及其他方法。因此,没有理由在使用它的唯一方法之外获取有关它的信息。我们还给出了一个在方法中声明本地类的示例,但这不是唯一的选择。它可以简单地在代码块中声明:
public class PhoneNumberValidator {
{
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
}
public void validatePhoneNumber(String phoneNumber) {
// ...number validation code
}
}
甚至在for
循环中!
public class PhoneNumberValidator {
public void validatePhoneNumber(String phoneNumber) {
for (int i = 0; i < 10; i++) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
// ...some logic
}
// ...number validation code
}
}
但这种情况极为罕见。在大多数情况下,声明将发生在方法内部。于是,我们弄明白了声明,也谈起了“哲学”:)局部类与内部类相比,有哪些额外的特点和区别? 不能在声明它的方法或块之外创建局部类的对象。 想象一下,我们需要一个generatePhoneNumber()
方法来生成随机电话号码并返回一个PhoneNumber
对象。在我们目前的情况下,我们无法在验证器类中创建这样的方法:
public class PhoneNumberValidator {
public void validatePhoneNumber(String number) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
// ...number validation code
}
// Error! The compiler does not recognize the PhoneNumber class
public PhoneNumber generatePhoneNumber() {
}
}
局部类的另一个重要特性是能够访问局部变量和方法参数。万一您忘记了,在方法内部声明的变量称为“局部”变量。也就是说,如果我们出于某种原因在方法String usCountryCode
内部创建一个局部变量validatePhoneNumber()
,我们可以从局部PhoneNumber
类访问它。然而,有很多微妙之处取决于程序中使用的语言版本。在课程开始时,我们注意到其中一个示例的代码可能无法在 Java 7 中编译,还记得吗?现在让我们考虑一下这样做的原因 :) 在 Java 7 中,局部类只有final
在方法中声明为局部变量或方法参数时才能访问它们:
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
class PhoneNumber {
private String phoneNumber;
// Error! The method parameter must be declared as final!
public PhoneNumber() {
this.phoneNumber = number;
}
public void printUsCountryCode() {
// Error! The local variable must be declared as final!
System.out.println(usCountryCode);
}
}
// ...number validation code
}
这里编译器产生两个错误。这里一切都井井有条:
public void validatePhoneNumber(final String number) {
final String usCountryCode = "+1";
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public void printUsCountryCode() {
System.out.println(usCountryCode);
}
}
// ...number validation code
}
现在您知道为什么本课开头的代码无法编译了:在 Java 7 中,局部类只能访问final
方法参数和final
局部变量。在 Java 8 中,本地类的行为发生了变化。在这个版本的语言中,本地类不仅可以访问final
本地变量和参数,还可以访问那些effective-final
. Effective-final
是一个变量,其值自初始化以来未更改。例如,在 Java 8 中,我们可以轻松地usCountryCode
在控制台上显示变量,即使它不是final
. 重要的是它的值不会改变。在以下示例中,一切正常:
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
class PhoneNumber {
public void printUsCountryCode() {
// Java 7 would produce an error here
System.out.println(usCountryCode);
}
}
// ...number validation code
}
但是如果我们在初始化后立即更改变量的值,代码将无法编译。
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
usCountryCode = "+8";
class PhoneNumber {
public void printUsCountryCode() {
// Error!
System.out.println(usCountryCode);
}
}
// ...number validation code
}
难怪局部类是内部类概念的亚种!他们也有共同的特点。 本地类可以访问外部类的所有(甚至私有)字段和方法:静态的和非静态的。 例如,让我们String phoneNumberRegex
向验证器类添加一个静态字段:
public class PhoneNumberValidator {
private static String phoneNumberRegex = "[^0-9]";
public void validatePhoneNumber(String phoneNumber) {
class PhoneNumber {
// ......
}
}
}
将使用此静态变量执行验证。该方法检查传递的字符串是否包含与正则表达式“”不匹配的字符[^0-9]
(即任何不是从 0 到 9 的数字的字符)。我们可以很容易地从本地PhoneNumber
类访问这个变量。比如写一个getter:
public String getPhoneNumberRegex() {
return phoneNumberRegex;
}
本地类类似于内部类,因为它们不能定义或声明任何静态成员。静态方法中的局部类只能引用封闭类的静态成员。例如,如果您没有将封闭类的变量(字段)定义为静态变量,则 Java 编译器会生成错误:“无法从静态上下文中引用非静态变量。” 本地类不是静态的,因为它们可以访问封闭块中的实例成员。因此,它们不能包含大多数类型的静态声明。您不能在块内声明接口:接口本质上是静态的。此代码无法编译:
public class PhoneNumberValidator {
public static void validatePhoneNumber(String number) {
interface I {}
class PhoneNumber implements I{
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
}
// ...number validation code
}
}
但是如果在外部类中声明了接口,则该类PhoneNumber
可以实现它:
public class PhoneNumberValidator {
interface I {}
public static void validatePhoneNumber(String number) {
class PhoneNumber implements I{
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
}
// ...number validation code
}
}
不能在本地类中声明静态初始化器(初始化块)或接口。但是本地类可以有静态成员,前提是它们是常量变量 ( static final
)。伙计们,现在你知道本地课程了!正如你所看到的,它们与普通的内部类有很多不同之处。我们甚至不得不深入研究该语言特定版本的特性以了解它们的工作原理:) 在下一课中,我们将讨论匿名内部类——最后一组嵌套类。祝你学业顺利!:)
GO TO FULL VERSION