Java中的内部类
- Java中的類與對象
- Java中的操作符
- Java中的程序控制流程
- Java中的初始化與清理
- Java中的訪問權限控制
- Java中的復用類
- Java中的多態
- Java中的抽象類與接口
- Java中的內部類
- Java中的容器
將一個類的定義放在另一個類的定義內部,這就是內部類;內部類允許你把一些邏輯相關的類組織在一起,并控制位于內部的類的可視性;
內部類的使用
1.1 創建內部類
把類的定義置于外圍類的里面:
public class Parcel{class Contents{private int i = 11;public int value(){ return i;}}public Contents contents(){return new Contents();}public static void main(String[] args){Parcel p = new Parcel();Parcel.Contents c = p.contents();}}其實上使用內部類與使用普通類相比較沒有什么不同,但是因為內部類是嵌套在類中的,我們可以在外部類中定義一個方法用來返回內部類中的引用。如果想從外部類的非靜態方法之外的任意位置創建某個內部類的對象,那么必須像在main()方法中那樣,具體地指明這個對象的類型:OuterClassName.InnerClassName
1.2 鏈接到外部類
內部類似乎只是一種名字隱藏和組織代碼的模式;當生成一個內部類的對象時,此對象與制造它的外圍對象之間就有了一種聯系,所以它能訪問其外圍對象的所有成員,而不需要任何特殊條件。
內部類還擁有其外圍類的所有成員的訪問權,內部類的對象只能在與其外圍類的對象相關聯的情況下才能被創建。構建內部類對象時,需要一個指向其外圍類對象的引用,如果編譯器訪問不到這個引用就會報錯。
1.3 生成對外部類對象的引用
如果你需要生成對外部類對象的引用,可以使用外部類的名字后面緊跟 .this。這樣產生的引用自動具有正確的類型,這一點在編譯期就被知曉并受到檢查,因此沒有任何運行時開銷。
在外部使用內部類的兩種方式:
1.3.1 在外圍類中定義一個方法,該方法返回內部類的一個對象,方法是public的
1.3.2 用外圍類的對象得到內部類對象
有時候你可能想要告知某些其他對象,去創建其某個內部類的對象。要實現此目的,你必須在new表達式中提供對其他外部類對象的引用,這時需要使用 .new語法:
public class DotNew{public class Inner{}public static void main(String[] args){DotNew dn = new DotNew();DotNew.Inner dni = dn.new Inner();} }要想直接創建內部類的對象,不能去引用外部類的名字DotNew,而是必須使用外部類的對象來創建該內部類對象。這也解決了內部類名字作用域的問題,因此你不必聲明,dn.new DotNew.Inner()。
在擁有外部類對象之前是不可能創建內部類對象的。這是因為內部類對象會暗暗地連接到創建它的外部類對象上。但是,如果你創建的是嵌套類(靜態內部類),那么它就不需要對外部類對象的引用。
1.4 內部類與向上轉型
當將內部類向上轉型為其基類,尤其是轉型為一個接口的時候,內部類就有了用武之地。這是因為此內部類–某個接口的實現–能夠完全不可見,并且不可用。所得到的只是指向基類或接口的引用,所以能夠很方便地隱蔽實現細節。
public interface Destination{String readLabel(); } public interface Contents{int value(); }現在Contents和Destination表示客戶端程序員可用的接口。當取的得了一個指向基類或接口的引用時,甚至可能無法找出它確切的類型:
class Parcel{private class PContents implements Contents{private int i =11;public int value(){return i;}}protected class PDestination implements Destination{private String label;private PDestination(String whereTo){label = whereTo;}public String readLabel(){ return label;}}public Destination destination(String s){return new PDestination(s); }public Contents contents(){return new PContents(); }} public class TestParcel{public static void main(String[] args){Parcel p = new Parcel();Contents c = p.contents();Destination d = p.destination("Tasmania");} }Parcel中增加了一些新東西:內部類PContents是private,所以除了Parcel,沒人能訪問它。PDestination是protected,所以只有Parcel及其子類、還有與Parcel同一個包中的類能訪問PDestination,其他類都不能訪問PDestination。這意味著,如果客戶端程序員想了解或訪問這些成員,那是受限制的。
private內部類給類的設計者提供了一種途徑,通過這種方式可以完全組織任何依賴于類型的編碼,并且完全隱藏了實現的細節。此外,從客戶端程序員的角度來看,由于不能訪問任何新增加的、原本不屬于公共接口的方法,所以擴展接口是沒有價值的。這也給Java編譯器提供了生成更高效代碼的機會。
內部類分類
上邊已經簡單介紹了內部類的使用,除了作為外部類的一個成員存在的內部類(成員內部類)以外還有其他形式的內部類:局部內部類、匿名內部類
2.1 局部內部類
在Java中,可以在一個方法里面或者在任意的作用域內定義內部類。這么做的兩個理由:
- 1.實現了某類型的接口,于是可以創建并返回對其的引用。
- 2.需要解決一個復雜的問題,想創建一個類來輔助解決方案,但是又不希望這個類是公共可用的。
在方法的作用域內創建一個完整的類,這稱作局部內部類:
public class Parcel5{interface Destination{}public Destination destination(String s){class PDestination implements Destination {private String label;private PDestination(String whereTo) {label = whereTo;}public String readLabel(){return label;}}return new PDestination(s);}public static void main(String[] args) {Parcel5 p = new Parcel5();Destination d = p.destination("Tasmania");}}在任意的作用域內嵌入一個內部類:
public class Parcel6 {private void internalTracking(boolean b){if(b){class TrackingSlip{private String id;TrackingSlip(String s){id=s;}String getSlip(){return id;}}TrackingSlip ts=new TrackingSlip("slip");String s=ts.getSlip();}}public void track(){internalTracking(true);}public static void main(String[] args) {Parcel6 p=new Parcel6();p.track();} }當我們需要一個已命名的構造器,或者需要重載構造器時我們會用到局部內部類,而不能使用匿名內部類,因為匿名內部類只能用于實例初始化。當我們需要不止一個該內部類的對象,也要采用局部內部類。
局部內部類不能有訪問說明符,因為它不是外圍類的一部分;但是它可以訪問當前代碼塊內的常量,以及此外圍類的所有成員。局部內部類的名字在方法外是不可見的。
2.2 匿名內部類
public class Parcel {interface Contents {}// class MyContents implements Contents { // private int i=11; // public int value(){return i;} // }public Contents contents() {return new Contents() {private int i = 11;public int value() {return i;}};// return new MyContents();}public static void main (String[]args){Parcel p = new Parcel();Contents c = p.contents();}}創建一個繼承自Contents的匿名類的對象,在這個匿名內部類中,使用了默認的構造器來生成Contents。如果定義一個匿名內部類,并且希望它使用一個在其外部定義的對象,那么編譯器會要求其參數是final的。否則,編譯器將會報錯。如果只是傳遞給匿名類的基類的構造器,那么不需要將傳入的形參定為final。(匿名內部類沒有構造器,只能用實例化代替。)
2.3 嵌套類
如果不需要內部類對象與其外圍類對象之間有聯系,那么可以將內部類聲明為static。這通常稱為嵌套類。普通的內部類對象隱式地保存了一個引用,指向創建它的外圍類對象。然而,當內部類是static的時,就會不一樣了。嵌套類意味著:
- 1.要創建嵌套類的對象,并不需要其外圍類的對象
- 2.不能從嵌套類的對象中訪問非靜態的外圍類對象
嵌套類與普通的內部類還有一個區別,普通內部類的字段與方法,只能放在類的外部層次上,所以普通的內部類不能有static數據和static字段,也不能包含嵌套類。
接口內部的類:
正常情況下,不能在接口內部放置任何代碼,但嵌套類可以作為接口的一部分。你放到接口中的任何類都自動地是public和static的。因為類是static的,只是將嵌套類置于接口的命名空間內,這并不違反接口的規則。你甚至可以在內部類中實現其外圍接口:
public interface ClassInInterface{void howdy();class Test implements ClassInInterface{public void howdy(){System.out.print("Howdy!");}public static void main(String[] args){new Test().howdy();}} }如果你想要創建某些公共代碼,使得它們可以被某個接口的所有不同實現所共用,那么使用接口內部的嵌套類會顯得很方便。
嵌套類的兩個有效用途:
- 1.實現每個類實現接口都需要一部分公共代碼
- 2.代替main方法對每個類進行測試
從多層嵌套類中訪問外部類的成員:
一個內部類被嵌套多少層并不重要—它能透明地訪問所有它所嵌入的外圍類的所有成員,如下所示:
可以看到,在MNA.A.B中,調用方法g()和f()不需要任何條件(即使它們被定義為private)。
其他
3.1 為什么需要內部類
一般來說,內部類繼承自某個類或實現某個接口,內部類的代碼操作創建它的外圍類的對象。所以可以認為內部類提供了某種進入其外圍類的窗口。
在外圍類中不是總能享用到接口帶來的方便,有時需要我們去實現接口。而使用內部類時,每個內部類都能獨立地繼承自一個接口的實現,所以無論外圍類是否已經繼承了某個接口的實現,對于內部類都沒有影響。如果沒有內部類提供的、可以繼承多個具體的或抽象的類的能力,一些設計與編程問題就很難解決。從這個角度看,內部類使得多重繼承的解決方案變得完整。
接口解決了部分問題,而內部類有效地實現了 “多重繼承”。也就是說,內部類允許繼承多個非接口類型(類或抽象類)。
使用內部類可以獲得的一些特性:
- 1.內部類可以有多個實例,每個實例都有自己的狀態信息,并且與其外圍類對象的信息相互獨立。
- 2.在單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口,或繼承同一個類。
- 3.創建內部類對象的時刻并不依賴于外圍類對象的創建。
- 4.內部類并沒有令人迷惑的“is-a”關系;它就是一個獨立的實體。
3.2 閉包與回調
閉包是一個可調用的對象,它記錄了一些信息,這些信息來自于創建它的作用域。通過這個定義,可以看出內部類是面向對象的閉包,因為它不僅包含外圍類(創建內部類的作用域)的信息,還自動擁有一個指向此外圍類對象的引用,在此作用域內,內部類有權操作所有的成員,包括private成員。通過內部類提供閉包的功能是優良的解決方案,它比指針更靈活、更安全。
3.3 內部類的繼承
因為內部類的構造器必須連接到指向其外圍類對象的引用,所以在繼承內部類的時候,事情就變得有點復雜。問題在于,那個指向外圍類對象的“秘密的”引用必須被初始化,而在導出類中不再存在可連續的默認對象。要解決這個問題,必須使用特殊的語法來明確說清它們之間的關聯:
class WithInner{class Inner {} }public classs InheritInner extends WithInner.Inner{InheritInner(WithInner wi){wi.super();}public static void main(String[] args){WithInner wi = new WithInner();InheritInner ii = new InheritInner(wi);} }可以看到,InheritInner只繼承自內部類,而不是外圍類。但是當要生成一個構造器時,默認的構造器并不算好,而且不能只是傳遞一個指向外圍類對象的引用。此外,必須在構造器內使用如下語法:
enclosingClassReference.super(); 這樣才能提供了必要的引用,然后程序才能編譯通過。
3.4 內部類的覆蓋問題
內部類不可以像普通方法覆蓋那樣進行覆蓋
覆蓋內部類的方法:首先外圍類繼承外圍類,然后內部類繼承外圍類(外圍類.內部類方式)
3.5 內部類標識符
由于每個類都會產生一個 .class文件,其中包含了如何創建該類型的對象的全部信息,內部類也必須生成一個 .class文件以包含它們的Class對象信息。
這些類文件的命名有嚴格的規則:外圍類的名字,加上 “$”,再加上內部類的名字。例如:LocalInnerClass.java生成的.class文件包括:
Counter.class
LocalInnerClass$1.class
LocalInnerClass$1LocalCounter.class
LocalInnerClass.class
如果內部類是匿名的,編譯器會簡單地產生一個數字作為其標識符。如果內部類是嵌套在別的內部類之中,只需要直接將它們的名字加在其外圍類標識符與"$"的后面。
總結
- 上一篇: 2022,2021英语六级全套资料自提,
- 下一篇: lc谐振计算机网络,LC谐振频率的测试方