摘要:鄙人最近嘗試著翻譯了自己的第一篇英文技術(shù)文檔。如果我們需要在其他外部類中使用內(nèi)部類,則一定要將嵌套類聲明為或者。方法中的會(huì)覆蓋掉內(nèi)部類中的。因此,對(duì)于一個(gè)內(nèi)部類序列化后,使用不同的進(jìn)行反序列化的話,可能會(huì)存在兼容性的問題。
鄙人最近嘗試著翻譯了自己的第一篇英文技術(shù)文檔。
Java Nested Classes Reference From Oracle Documentation
在Java中我們可以在一個(gè)類的內(nèi)部,再定義另外一個(gè)類,其中里面的那個(gè)類被稱為嵌套類,示例如下。
class OuterClass { ... class NestedClass { ... } }
術(shù)語:嵌套類有兩種類型:靜態(tài)和非靜態(tài),當(dāng)嵌套類被static修飾時(shí),被稱為靜態(tài)嵌套類(static nested classes),沒有被static修飾時(shí)的嵌套類被稱作內(nèi)部類(inner classes)
class OuterClass { ... static class StaticNestedClass { ... } class InnerClass { ... } }
嵌套類是外部基類(即外部類)的成員,非靜態(tài)嵌套類(內(nèi)部類)可以獲取到外圍基類的其他成員,其中也包括被聲明為private的成員。靜態(tài)嵌套類則不可以獲取基類的其他成員。當(dāng)做為作為外部類的成員,嵌套類可以被定義為private,public,protected或者package private。如果我們需要在其他外部類中使用內(nèi)部類,則一定要將嵌套類聲明為public或者 package private。
為什么使用嵌套類-Why Use Nested Classes?使用嵌套類有以下幾個(gè)明顯的優(yōu)勢:
當(dāng)僅會(huì)在一處用到某個(gè)類時(shí),通過嵌套類可以在邏輯上與基類(外部類)保持一種緊密的聯(lián)系關(guān)系:當(dāng)一個(gè)類只會(huì)在另一個(gè)類中使用,那么就可以把這個(gè)類嵌入到另外一個(gè)類中,可以使得兩者之間有著緊密的聯(lián)系,嵌套類又稱之為"輔助類"。
通過合理的使用可以使得整個(gè)包下的類定義更加的簡潔:更強(qiáng)的封裝性:A和B兩個(gè)類,B作為A類的嵌套類,如果不將其中B類B類設(shè)置為private的話,那么B類就擁有訪問A類成員的權(quán)限。
更好的可讀性和更高的可維護(hù)性:在編碼時(shí)內(nèi)部的嵌套類總是需要和最外層類保持一種形式上的關(guān)聯(lián)關(guān)系。
靜態(tài)嵌套類-Static Nested Classes靜態(tài)嵌套類不能直接引用外部基類的實(shí)例變量和實(shí)例方法,對(duì)于這樣的實(shí)例變量僅可以通過對(duì)象引用來獲取。
通過使用外圍基類名稱來獲取靜態(tài)嵌套類
OuterClass.StaticNestedClass
如果我們想創(chuàng)建一個(gè)靜態(tài)嵌套類的對(duì)象,則可以使用如下的方式
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();內(nèi)部類-Inner Classes
內(nèi)部類可以通過外部類實(shí)例,直接獲取基類對(duì)象的變量和方法,同理因?yàn)閮?nèi)部類是通過實(shí)例引用來和外部類建立關(guān)系的,所以在內(nèi)部類中不能定義任何的靜態(tài)成員。只有當(dāng)外部類實(shí)例對(duì)象被創(chuàng)建出來之后,才可以實(shí)例化內(nèi)部類。
class OuterClass { ... class InnerClass { ... } }
內(nèi)部類實(shí)例只能存在于外部類實(shí)例中,并且可以直接訪問其外部類實(shí)例的方法和字段。
在實(shí)例化內(nèi)部類前,要先實(shí)例化外部類實(shí)例??梢酝ㄟ^如下方式,通過外部對(duì)象實(shí)例來創(chuàng)建內(nèi)部類對(duì)象。
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
內(nèi)部類有兩種類型:局部類(local classes) 和 匿名類(anonymous classes).
局部類-Local Classes局部類是一種被定義在代碼塊中的類,局部類通常時(shí)定義在方法體中。
如何聲明局部類:可以在任何一個(gè)方法之中定義一個(gè)局部類,如for循環(huán)中,或者在if子句中。
下面的LocalClassExample,是用來驗(yàn)證兩個(gè)手機(jī)號(hào),在這個(gè)類的validatePhoneNumber方法中,定義了一個(gè)名為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"); } }
通過刪除原有手機(jī)號(hào)中除0-9之外的字符后,檢查新的字符串中是否有十個(gè)數(shù)字,輸出結(jié)果如下:
First number is 1234567890 Second number is invalid獲取外部類成員
局部類可以獲取外部類的成員信息,在上一個(gè)例子中,PhoneNumber局部類的構(gòu)造方法里通過LocalClassExample.regularExpression,就拿到了外部類中的regularExpression成員。
另外,局部類中也能使用局部變量,但是在局部類中只能使用被final修飾后的變量,當(dāng)一個(gè)局部類要使用定義在外部代碼塊中的局部變量或者參數(shù)時(shí),他會(huì)俘獲(這個(gè)變量就是他的了)這個(gè)變量或者參數(shù)。
比如,PhoneNumber的構(gòu)造方法中,能夠/會(huì),俘獲numberLength,因?yàn)檫@個(gè)變量在外圍塊中被聲明為final,這樣的話numberLength 就成為了一個(gè)被俘獲的變量了,有了主人。
但是在java 1.8版本中局部類能夠使用定義在外部塊中的final或者effectively final的變量或者參數(shù),如果一個(gè)變量或者參數(shù)的值在初始化后便不會(huì)被改變,則被稱為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; }
因?yàn)檫@個(gè)賦值語句numberLength = 7,變量numberLength 便不再是 effectively final了,在這種情形下,內(nèi)部類嘗試在if (currentNumber.length() == numberLength)這行代碼中獲取numberLength時(shí),編譯器時(shí)會(huì)提示"local variables referenced from an inner class must be final or effectively final"。
在java8中,如果在方法中聲明了局部類,那么可以在局部類中拿到方法的入?yún)?,就像下面的方法?/p>
public void printOriginalNumbers() { System.out.println("Original numbers are " + phoneNumber1 + " and " + phoneNumber2); }
局部類中的printOriginalNumbers方法獲取到了方法validatePhoneNumber中的phoneNumber1 和phoneNumber2兩個(gè)參數(shù)變量。
局部類與內(nèi)部類的相似點(diǎn)局部類像內(nèi)部類一樣,二者都不能定義和聲明靜態(tài)成員,在靜態(tài)方法validatePhoneNumber中定義的PhoneNumber局部類,只能引用外部類中的靜態(tài)成員。
如果將變量regularExpression定義為非靜態(tài),那么在java編譯器編譯的時(shí)候會(huì)提示"non-static variable regularExpression cannot be referenced from a static context."錯(cuò)誤信息。
因?yàn)橐@取外圍代碼塊中的實(shí)例成員,所以局部類不能時(shí)靜態(tài)的,所以在局部類中不能包含有靜態(tài)聲明。
不能在代碼塊中,嘗試定義或者聲明接口,因?yàn)榻涌诒举|(zhì)上就是靜態(tài)的,比如下面的代碼是不能編譯成功的,因?yàn)樵趃reetInEnglish方法內(nèi)部包含有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(); }
當(dāng)然在局部類中也不能聲明靜態(tài)方法,下面的代碼同樣,在編譯時(shí)會(huì)報(bào)"modifier "static" is only allowed in constant variable declaration",因?yàn)镋nglishGoodbye.sayGoodbye這個(gè)方法被聲明為靜態(tài)方法了。
public void sayGoodbyeInEnglish() { class EnglishGoodbye { public static void sayGoodbye() { System.out.println("Bye bye"); } } EnglishGoodbye.sayGoodbye(); }
局部類中只有變量時(shí)常量的時(shí)候,才可能會(huì)出現(xiàn)有靜態(tài)成員變量的情況,下面的代碼中有靜態(tài)成員但也可以編譯通過,因?yàn)殪o態(tài)變量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
匿名類可以使你的代碼看上去更加的精簡,可以在聲明一個(gè)匿名類的同時(shí)對(duì)它進(jìn)行初始化,除了沒有類名以外,它跟局部類很像,對(duì)于只會(huì)使用一次的局部類的場景我們可以用匿名類來代替。
局部類就是一個(gè)類,而匿名類則更像是一個(gè)表達(dá)式,那么我們便可以在另外的表達(dá)式中使用匿名類。
下面的例子中 HelloWorldAnonymousClasses通過使用匿名類創(chuàng)建局部變量frenchGreeting 和spanishGreeting,通過使用局部類來創(chuàng)建和初始化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(); } }如何使用和定義一個(gè)匿名類
我們可以通過frenchGreeting的創(chuàng)建過程來一探匿名類的組成。
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 操作符
要實(shí)現(xiàn)的接口名,或者要繼承的父類的名稱,在此例中匿名類實(shí)現(xiàn)了HelloWorld接口。
括號(hào),跟一般初始化一個(gè)類實(shí)例別無二致,需要填入構(gòu)造方法中的構(gòu)造參數(shù),注:用匿名類實(shí)現(xiàn)接口時(shí),沒有構(gòu)造方法,那么括號(hào)中不需要填參數(shù)即可。
類主體,即匿名類的實(shí)現(xiàn)。
因?yàn)槟涿惐划?dāng)做表達(dá)式一樣被使用,如在定義frenchGreeting對(duì)象時(shí),匿名類的全部定義都是該表達(dá)式的一部分, 這也解釋了為什么匿名類定義的最后要以;結(jié)尾,因?yàn)楸磉_(dá)式以分號(hào);結(jié)尾。
訪問外部類的局部變量、聲明和使用匿名類成員像局部類一樣,匿名類同樣也可以俘獲變量,對(duì)于外部區(qū)域的局部變量擁有一樣的訪問特性。
匿名類可以訪問外部其封閉類的成員
匿名類無法訪問那些不是final或者effectively final的局部變量
匿名類中的聲明的類型變量,會(huì)覆蓋掉外部區(qū)域中的同名的變量
對(duì)于匿名類中的成員,匿名類具有跟局部類相同的限制
不能在匿名類中聲明靜態(tài)代碼塊,或者再定義內(nèi)部成員接口
匿名類中僅當(dāng)變量為常量時(shí),才可以出現(xiàn)靜態(tài)成員
小結(jié),在匿名類中可以聲明如下內(nèi)容
列表項(xiàng)目
字段
額外的方法(即使不實(shí)現(xiàn)任何父類的方法)
實(shí)例代碼塊
局部類
但是,不可以在匿名類中聲明構(gòu)造方法
匿名類的一個(gè)實(shí)例匿名類在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(); } }
在內(nèi)部類或者方法定義中聲明的變量類型跟外圍區(qū)域有相同的名稱,那么內(nèi)部的聲明會(huì)覆蓋掉外部區(qū)域中的聲明,不能直接通過變量名拿到外部區(qū)域中定義的變量,如下所示:
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
示例代碼中定義了三個(gè)名為x的變量,ShadowTest中的成員變量,內(nèi)部類FirstLevel中成員變量,以及方法methodInFirstLevel中的參數(shù)。
方法methodInFirstLevel中的x會(huì)覆蓋掉內(nèi)部類FirstLevel中的x。因?yàn)楫?dāng)你在方法methodInFirstLevel中使用變量x時(shí),實(shí)際上使用的的是方法參數(shù)的值。
如果想引用內(nèi)部類FirstLevel中的x,需要使用this關(guān)鍵字,來代表引用的時(shí)內(nèi)部類中方法外圍的x。
System.out.println("this.x = " + this.x);
如果向引用最外面的基類變量x,則需要指明外部類的類名
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);序列化問題-Serialization
我們強(qiáng)烈不建議對(duì)內(nèi)部類、局部類及匿名類,實(shí)現(xiàn)序列化。
當(dāng)Java編譯器編譯內(nèi)部類的構(gòu)造方法時(shí),會(huì)生成synthetic constructs。即一些在源碼中未曾出現(xiàn)過的類、方法、字段和其他的構(gòu)造方法也會(huì)被編譯出來。
synthetic constructs方式,可以在不改變JVM的前提下,只通過java編譯器就可以實(shí)現(xiàn)java的新特性。然而,不同的編譯器實(shí)現(xiàn)synthetic constructs的方式有所不同,這也就意味著,對(duì)于同樣的.java源碼,不同的編譯器會(huì)編譯出來不同的.class文件。
因此,對(duì)于一個(gè)內(nèi)部類序列化后,使用不同的JRE進(jìn)行反序列化的話,可能會(huì)存在兼容性的問題。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/72583.html
摘要:嵌套類增加了封裝性內(nèi)部類和靜態(tài)嵌套類的不同根源來自于,最大區(qū)別在于訪問外部類成員的權(quán)限。靜態(tài)嵌套類修飾符使得嵌套類對(duì)象成為外部類的靜態(tài)成員,與外部類直接關(guān)聯(lián)。 術(shù)語規(guī)范:按照官方文檔,定義在外部類(封裝類)內(nèi)部的類稱之為nested class,根據(jù)是否被static關(guān)鍵字修飾又分為兩類:static nested classes 和 inner classes。 class Oute...
摘要:地址前面一個(gè)部分講解了如何使用工具來測試項(xiàng)目,現(xiàn)在我們講解如何使用工具來測試項(xiàng)目。所以我們可以利用這個(gè)特性來進(jìn)一步簡化測試代碼。因?yàn)橹挥羞@樣才能夠在測試環(huán)境下發(fā)現(xiàn)生產(chǎn)環(huán)境的問題,也避免出現(xiàn)一些因?yàn)榕渲貌煌瑢?dǎo)致的奇怪問題。 Github地址 前面一個(gè)部分講解了如何使用Spring Testing工具來測試Spring項(xiàng)目,現(xiàn)在我們講解如何使用Spring Boot Testing工具來測...
閱讀 2201·2023-04-26 00:00
閱讀 3345·2021-09-24 10:37
閱讀 3567·2021-09-07 09:58
閱讀 1555·2019-08-30 15:56
閱讀 2247·2019-08-30 13:11
閱讀 2339·2019-08-29 16:38
閱讀 1025·2019-08-29 12:58
閱讀 1926·2019-08-27 10:54