你好!再來說說另一種嵌套類。我說的是本地類(方法本地內部類)。在深入研究之前,我們必須首先記住它們在嵌套類結構中的位置。 從我們的圖中可以看出,局部類是內部類的一個亞種,我們在之前的資料
中詳細講過。然而,局部類與普通內部類有許多重要的特徵和區別。最主要的是在他們的聲明中:本地類只在代碼塊中聲明。大多數情況下,此聲明位於外部類的某些方法中。 例如,它可能看起來像這樣:

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