摘要:鄙人最近嘗試著翻譯了自己的第一篇英文技術文檔。如果我們需要在其他外部類中使用內部類,則一定要將嵌套類聲明為或者。方法中的會覆蓋掉內部類中的。因此,對于一個內部類序列化后,使用不同的進行反序列化的話,可能會存在兼容性的問題。
鄙人最近嘗試著翻譯了自己的第一篇英文技術文檔。
Java Nested Classes Reference From Oracle Documentation
在Java中我們可以在一個類的內部,再定義另外一個類,其中里面的那個類被稱為嵌套類,示例如下。
class OuterClass { ... class NestedClass { ... } }
術語:嵌套類有兩種類型:靜態和非靜態,當嵌套類被static修飾時,被稱為靜態嵌套類(static nested classes),沒有被static修飾時的嵌套類被稱作內部類(inner classes)
class OuterClass { ... static class StaticNestedClass { ... } class InnerClass { ... } }
嵌套類是外部基類(即外部類)的成員,非靜態嵌套類(內部類)可以獲取到外圍基類的其他成員,其中也包括被聲明為private的成員。靜態嵌套類則不可以獲取基類的其他成員。當做為作為外部類的成員,嵌套類可以被定義為private,public,protected或者package private。如果我們需要在其他外部類中使用內部類,則一定要將嵌套類聲明為public或者 package private。
為什么使用嵌套類-Why Use Nested Classes?使用嵌套類有以下幾個明顯的優勢:
當僅會在一處用到某個類時,通過嵌套類可以在邏輯上與基類(外部類)保持一種緊密的聯系關系:當一個類只會在另一個類中使用,那么就可以把這個類嵌入到另外一個類中,可以使得兩者之間有著緊密的聯系,嵌套類又稱之為"輔助類"。
通過合理的使用可以使得整個包下的類定義更加的簡潔:更強的封裝性:A和B兩個類,B作為A類的嵌套類,如果不將其中B類B類設置為private的話,那么B類就擁有訪問A類成員的權限。
更好的可讀性和更高的可維護性:在編碼時內部的嵌套類總是需要和最外層類保持一種形式上的關聯關系。
靜態嵌套類-Static Nested Classes靜態嵌套類不能直接引用外部基類的實例變量和實例方法,對于這樣的實例變量僅可以通過對象引用來獲取。
通過使用外圍基類名稱來獲取靜態嵌套類
OuterClass.StaticNestedClass
如果我們想創建一個靜態嵌套類的對象,則可以使用如下的方式
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();內部類-Inner Classes
內部類可以通過外部類實例,直接獲取基類對象的變量和方法,同理因為內部類是通過實例引用來和外部類建立關系的,所以在內部類中不能定義任何的靜態成員。只有當外部類實例對象被創建出來之后,才可以實例化內部類。
class OuterClass { ... class InnerClass { ... } }
內部類實例只能存在于外部類實例中,并且可以直接訪問其外部類實例的方法和字段。
在實例化內部類前,要先實例化外部類實例。可以通過如下方式,通過外部對象實例來創建內部類對象。
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
內部類有兩種類型:局部類(local classes) 和 匿名類(anonymous classes).
局部類-Local Classes局部類是一種被定義在代碼塊中的類,局部類通常時定義在方法體中。
如何聲明局部類:可以在任何一個方法之中定義一個局部類,如for循環中,或者在if子句中。
下面的LocalClassExample,是用來驗證兩個手機號,在這個類的validatePhoneNumber方法中,定義了一個名為PhoneNumber的局部類。
public class LocalClassExample { static String regularExpression = "[^0-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,就拿到了外部類中的regularExpression成員。
另外,局部類中也能使用局部變量,但是在局部類中只能使用被final修飾后的變量,當一個局部類要使用定義在外部代碼塊中的局部變量或者參數時,他會俘獲(這個變量就是他的了)這個變量或者參數。
比如,PhoneNumber的構造方法中,能夠/會,俘獲numberLength,因為這個變量在外圍塊中被聲明為final,這樣的話numberLength 就成為了一個被俘獲的變量了,有了主人。
但是在java 1.8版本中局部類能夠使用定義在外部塊中的final或者effectively final的變量或者參數,如果一個變量或者參數的值在初始化后便不會被改變,則被稱為effectively final。
比如在下面的代碼中,變量numberLength沒有被顯示的聲明為final,在初始化后有在方法中又將numberLength的值修改為7:
PhoneNumber(String phoneNumber) { numberLength = 7; String currentNumber = phoneNumber.replaceAll( regularExpression, ""); if (currentNumber.length() == numberLength) formattedPhoneNumber = currentNumber; else formattedPhoneNumber = null; }
因為這個賦值語句numberLength = 7,變量numberLength 便不再是 effectively final了,在這種情形下,內部類嘗試在if (currentNumber.length() == numberLength)這行代碼中獲取numberLength時,編譯器時會提示"local variables referenced from an inner class must be final or effectively final"。
在java8中,如果在方法中聲明了局部類,那么可以在局部類中拿到方法的入參,就像下面的方法:
public void printOriginalNumbers() { System.out.println("Original numbers are " + phoneNumber1 + " and " + phoneNumber2); }
局部類中的printOriginalNumbers方法獲取到了方法validatePhoneNumber中的phoneNumber1 和phoneNumber2兩個參數變量。
局部類與內部類的相似點局部類像內部類一樣,二者都不能定義和聲明靜態成員,在靜態方法validatePhoneNumber中定義的PhoneNumber局部類,只能引用外部類中的靜態成員。
如果將變量regularExpression定義為非靜態,那么在java編譯器編譯的時候會提示"non-static variable regularExpression cannot be referenced from a static context."錯誤信息。
因為要獲取外圍代碼塊中的實例成員,所以局部類不能時靜態的,所以在局部類中不能包含有靜態聲明。
不能在代碼塊中,嘗試定義或者聲明接口,因為接口本質上就是靜態的,比如下面的代碼是不能編譯成功的,因為在greetInEnglish方法內部包含有HelloThere接口:
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(); }
當然在局部類中也不能聲明靜態方法,下面的代碼同樣,在編譯時會報"modifier "static" is only allowed in constant variable declaration",因為EnglishGoodbye.sayGoodbye這個方法被聲明為靜態方法了。
public void sayGoodbyeInEnglish() { class EnglishGoodbye { public static void sayGoodbye() { System.out.println("Bye bye"); } } EnglishGoodbye.sayGoodbye(); }
局部類中只有變量時常量的時候,才可能會出現有靜態成員變量的情況,下面的代碼中有靜態成員但也可以編譯通過,因為靜態變量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(); }匿名類-Anonymous Classes
匿名類可以使你的代碼看上去更加的精簡,可以在聲明一個匿名類的同時對它進行初始化,除了沒有類名以外,它跟局部類很像,對于只會使用一次的局部類的場景我們可以用匿名類來代替。
局部類就是一個類,而匿名類則更像是一個表達式,那么我們便可以在另外的表達式中使用匿名類。
下面的例子中 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或者effectively final的局部變量
匿名類中的聲明的類型變量,會覆蓋掉外部區域中的同名的變量
對于匿名類中的成員,匿名類具有跟局部類相同的限制
不能在匿名類中聲明靜態代碼塊,或者再定義內部成員接口
匿名類中僅當變量為常量時,才可以出現靜態成員
小結,在匿名類中可以聲明如下內容
列表項目
字段
額外的方法(即使不實現任何父類的方法)
實例代碼塊
局部類
但是,不可以在匿名類中聲明構造方法
匿名類的一個實例匿名類在java GUI中使用的較為頻繁
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變量覆蓋問題-Shadowing() { @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(); } }
在內部類或者方法定義中聲明的變量類型跟外圍區域有相同的名稱,那么內部的聲明會覆蓋掉外部區域中的聲明,不能直接通過變量名拿到外部區域中定義的變量,如下所示:
public class ShadowTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { System.out.println("x = " + x); System.out.println("this.x = " + this.x); System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); } } public static void main(String... args) { ShadowTest st = new ShadowTest(); ShadowTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }
輸出如下
x = 23 this.x = 1 ShadowTest.this.x = 0
示例代碼中定義了三個名為x的變量,ShadowTest中的成員變量,內部類FirstLevel中成員變量,以及方法methodInFirstLevel中的參數。
方法methodInFirstLevel中的x會覆蓋掉內部類FirstLevel中的x。因為當你在方法methodInFirstLevel中使用變量x時,實際上使用的的是方法參數的值。
如果想引用內部類FirstLevel中的x,需要使用this關鍵字,來代表引用的時內部類中方法外圍的x。
System.out.println("this.x = " + this.x);
如果向引用最外面的基類變量x,則需要指明外部類的類名
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);序列化問題-Serialization
我們強烈不建議對內部類、局部類及匿名類,實現序列化。
當Java編譯器編譯內部類的構造方法時,會生成synthetic constructs。即一些在源碼中未曾出現過的類、方法、字段和其他的構造方法也會被編譯出來。
synthetic constructs方式,可以在不改變JVM的前提下,只通過java編譯器就可以實現java的新特性。然而,不同的編譯器實現synthetic constructs的方式有所不同,這也就意味著,對于同樣的.java源碼,不同的編譯器會編譯出來不同的.class文件。
因此,對于一個內部類序列化后,使用不同的JRE進行反序列化的話,可能會存在兼容性的問題。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72583.html
摘要:地址前面一個部分講解了如何使用工具來測試項目,現在我們講解如何使用工具來測試項目。所以我們可以利用這個特性來進一步簡化測試代碼。因為只有這樣才能夠在測試環境下發現生產環境的問題,也避免出現一些因為配置不同導致的奇怪問題。 Github地址 前面一個部分講解了如何使用Spring Testing工具來測試Spring項目,現在我們講解如何使用Spring Boot Testing工具來測...
閱讀 2155·2023-04-26 00:00
閱讀 3255·2021-09-24 10:37
閱讀 3532·2021-09-07 09:58
閱讀 1525·2019-08-30 15:56
閱讀 2221·2019-08-30 13:11
閱讀 2315·2019-08-29 16:38
閱讀 965·2019-08-29 12:58
閱讀 1883·2019-08-27 10:54