java程式設計思想-10內部類
簡單來說,將一個類的定義放在另一個類的定義內部,這就是內部類。內部類是一種非常有用的特性,因為它允許你把一些邏輯相關的類組織在一起,並控制位於內部的類的可視性。
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){
Pracel p = new Pracel();
Pracel.Contents c = p.contents();
}
}
如果想從外部類的非靜態方法之外的任意位置建立某個內部類的物件,那麼必須像在main()方法中那樣,具體地指明這個物件的型別:OuterClassName.InnerClassName。
2、連結到外部類
當生成一個內部類的物件時,此物件與製造它的外圍物件之間就有了一種聯絡。所以它能訪問其外圍物件的所有成員,而不需要任何特殊條件。此外,內部類還擁有其外圍類的所有元素的訪問權
這是如何做到的呢?當某個外圍類的物件建立了一個內部類物件時,此內部類物件必定會祕密地捕獲一個指向那個外圍類物件的引用。然後,在你訪問此外圍類的成員時,就是那個引用來選擇外圍類的成員。
3、使用 .this 和 .new
如果你需要生成對外部類物件的引用,可以使用外部類的名字後面緊跟圓點和this。這樣產生的引用自動具有正確的型別,這一點在編譯期就被知曉並受到檢查,因此沒有任何執行時開銷。
public class DotThis{
void f(){
System.out.println("DotThis.f()");
}
public class Inner{
public DotThis outer{
return DotThis.this;
}
}
public Inner inner(){
return new Inner();
}
public static void main(String[] args){
DotThis dt = new DotThis();
DontThis.Inner dti = dt.inner();
dti.outer().f();
}
}
但有時你可能想要告知某些其他物件,去建立其某個內部類的物件。要實現此目的,你必須在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();
在擁有外部類物件之前是不可能建立內部類物件的。這是因為內部類物件會暗暗地連線到建立它的外部類物件上。但是,如果你建立的是巢狀類(靜態內部類),那麼它就不需要對外部類物件的引用。
4、內部類與向上轉型
當將內部類向上轉型為其基類,尤其是轉型為一個介面的時候,內部類就有了用武之地。(從實現了某個介面的物件,得到對此介面的引用,與向上轉型為這個物件的基類,實質上效果是一樣的)這是因為此內部類—某個介面的實現—能夠完全不可見,並且不可用。所得到的只是指向基類或介面的引用,所以能夠很方便地隱藏實現細節。
我們可以建立一個示例的介面:
public interface Destination{
String readLabel();
}
public interface Contents{
int value();
}
現在Contents 和Destination表示客戶端程式設計師可用的介面。當取得了一個指向基類或介面的引用時,甚至可能無法找出它確切的型別。
class Parcel4{
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){
Parcel4 p = new Parcel4();
Contents c = p.contents();
Destination d = p.destination("Tamsmania");
}
}
Parcel4中增加了一些新東西:內部類PContents是private,所以除了Parcel4,沒人能訪問它。PDestination是protected,所以只有Parcel4及其子類、還有與Parcel4同一個包中的類(因為protected也給予了包訪問權)能訪問PDestination。這意味著,如果客戶端程式設計師想了解或者訪問這些成員,那是要受到限制的。實際上,甚至不能向下轉型成private內部類(或protected內部類,除非是繼承自它的子類),因為不能訪問其名字。於是,private內部類給類的設計者提供了一種途徑,通過這種方式可以完全阻止任何依賴於型別的編碼,並且完全隱藏了實現的細節。此外,從客戶端程式設計師的角度來說,由於不能訪問任何新增加的,原本不屬於公共介面的方法,所以擴充套件介面是沒有價值的。
5、在方法和作用域內的內部類
可以在一個方法裡面或在任意的作用域內定義內部類。這麼做有兩個理由:1).如前所示,你實現了某型別的介面,於是可以建立並返回對其的引用。2).你要解決一個複雜的問題,想建立一個類來輔助你的解決方案,但是又不希望這個類是公共可用的。
6、匿名內部類
public class Parcel7{
public Contents contents(){
return new Contents(){
private int i = 11;
public int value(){return i;}
}
}
public static void main(String[] args){
Parcel7 p =new Parcel7();
Contents c = p.contents();
}
}
contents()方法將返回值的生成與表示這個返回值的類的定義結合在一起。另外,這個類是匿名的,它沒有名字。更糟的是,看起來似乎是你正要建立一個Contents物件。但是然後(在到達語句結束的分號之前)你卻說:“等一等,我想在這裡插入一個類的定義”。這種奇怪的語法指的是:“建立一個繼承自Contents的匿名類的物件。”通過new表示式返回的引用被自動向上轉型為對Contents的引用。上述匿名內部類的語法是下述形式的簡化形式:
public class Parcel7b{
class Mycontents implements Contents{
private int i = 11;
public int value() { return i;}
}
public Contents contents(){
return new MyContents();
}
public static void main(String[] args){
Parcel7b p = new Parcel7b();
Contents c = p.contents();
}
}
如果定義一個匿名內部類,並且希望它使用一個在其外部定義的物件,那麼編譯器會要求其引數是final的。否則,編譯器將會報錯。如果只是傳遞給匿名類的基類的構造器,那麼不需要將傳入的形參定為final。
改進工廠方法:
interface Service{
void method1();
void method2();
}
interface ServiceFactory{
Service getService();
}
class Implementation1 implements Service{
private Implementation1(){}
public void method1() { System.out.print("Implementation1 method1");}
public void method2() { System.out.print("Implementation1 method2");}
public static void ServiceFactory factory =
new ServiceFactory(){
public Service getService(){
return new Implementation1();
}
}
}
class Implementation2 implements Service{
private Implementation2(){}
public void method1() { System.out.print("Implementation2 method1");}
public void method2() { System.out.print("Implementation2 method2");}
public static void ServiceFactory factory =
new ServiceFactory(){
public Service getService(){
return new Implementation2();
}
}
}
public class Factories{
public static void serviceConsumer(ServiceFactory fact){
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args){
serviceConsumer(Implementation1.factory);
serviceConsumer(Implementation2.factory);
}
}
現在用於Implementation1和Implementation2的構造器都可以private的。並且沒有任何必要去建立作為工廠的具名類。另外,你經常只需要單一的工廠物件。因此在本例中它被建立為Service實現中的一個static域。
7、巢狀類
如果不需要內部類物件與其外圍類物件之間有聯絡,那麼可以將內部類宣告為static。這通常被稱為巢狀類。想要理解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();
}
}
}
從多層巢狀類中訪問外部類的成員:一個內部類被巢狀多少層並不重要–它能透明地訪問所有它所嵌入的外圍類的所有成員。
class MNA{
private void f(){}
class A{
private void g() {}
public class B{
void h(){
g();
f();
}
}
}
}
public class MultiNestingAccess{
public static void main(String[] args){
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
8、為什麼需要內部類
一般來說,內部類繼承自某個類或實現某個介面,內部類的程式碼操作建立它的外圍類的物件。所以可以認為內部類提供了某種進入其外圍類的視窗。每個內部類都能獨立地繼承自一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。
如果沒有內部類提供的、可以繼承多個具體的或抽象的類的能力,一些設計與程式設計問題就很難解決。從這個角度看,內部類使得多重繼承的解決方案變得完整。介面解決了部分問題。而內部類有效地實現了“多重繼承”。也就是說,內部類允許繼承多個非介面型別(類或抽象類)。
當必須在一個類中以某種方式實現兩個介面。由於介面的靈活性,你有兩種選擇:使用單一類,或者使用內部類。
如果擁有的是抽象的類或具體的類,而不是介面,那就只能使用內部類才能實現多重繼承。
如果不需要解決“多重繼承”的問題,那麼自然可以用別的方式編碼,而不需要使用內部類。但如果使用內部類,還可以獲得一些其他的特性:
1)、內部類可以有多個例項,每個例項都有自己的狀態資訊。並且與其外圍類物件的資訊相互獨立。
2)、在單個外圍類中,可以讓多個內部類以不同的方式實現同一個介面,或繼承同一個類。
3)、建立內部類物件的時刻並不依賴於外圍類物件的建立。
4)、內部類並沒有令人迷惑的“is-a”關係;它就是一個獨立的實體。
閉包與回撥:閉包是一個可呼叫的物件,它記錄了一些資訊,這些資訊來自於建立它的作用域。通過這個定義,可以看出內部類是面向物件的閉包,因為它不僅包含外圍類物件(建立內部類的作用域)的資訊,還自動擁有一個指向此外圍類物件的引用,在此作用域內,內部類有權操作所有的成員,包括private成員。
9、內部類的繼承
因為內部類的構造器必須連線到指向其外圍類物件的引用,所以在繼承內部類的時候,事情會變得有點複雜。問題在於,那個指向外圍類的“祕密的”引用必須被初始化,而在匯出類中不再存在可連線的預設物件。要解決這個問題,必須使用特殊的語法來明確說清它們之間的關聯:
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();這樣才提供了必要的引用,然後程式才能編譯通過。
10、內部類可以被覆蓋嗎?
class Egg{
private Yolk y;
protected class Yolk{
public Yolk(){ Systrm.out.print("Egg.Yolk()");}
}
public Egg(){
System.out.print("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg{
public class Yolk{
public Yolk(){
Systrm.out.print("BigEgg.Yolk()");
}
}
public static void main(String[] args){
new Egg();
}
}
/Output:
New Egg()
Egg.Yolk()
預設的構造器是編譯器自動生成的,這裡是呼叫基類的預設構造器。你可能認為既然建立了BigEgg的物件,那麼所使用的應該是“覆蓋後”的Yolk版本。但從輸出中可以看到實際情況並不是這樣。這個例子說明,當繼承了某個外圍類的時候,內部類並沒有發生什麼特別神奇的變化。這兩個內部類完全是獨立的兩個實體。各自在自己的名稱空間內。當然,明確地繼承某個內部類也是可以的:
class Egg2{
protected class Yolk{
public Yolk() { System.out.print("Egg2.Yolk()");}
public void f() { System.out.print("Egg2.Yolk.f()");}
}
private Yolk y = new Yolk();
public Egg2() { System.out.print("New Egg2()"); }
public void insertYolk(Yolk yy ){ y=yy;}
public void g() { y.f(); }
}
public class BigEgg2 extends Egg2{
public class Yolk extends Egg2.Yolk{
public Yolk() { System.out.print("BigEgg2.Yolk()");}
public void f() { System.out.print("BigEgg2.Yolk.f()");}
}
public BigEgg2() { insertYolk(new Yolk()); }
public static void main(Stringp[] args){
Egg2 e2 = new BigEgg2();
e2.g();
}
}
/Output:
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()
11、區域性內部類
可以在程式碼塊中建立內部類。典型的方式是在一個方法體的裡面建立。區域性內部類不能有訪問說明符,因為它不是外圍類的一部分。但是它可以訪問當前程式碼塊內的常量以及此外圍類的所有成員。
12、內部類識別符號
由於每個類都會產生一個.class檔案,其中包含了如何建立該型別的物件的全部資訊。(此資訊產生一個“meta-class”,叫做Class物件)。內部類也必須生成一個.class檔案以包含它們的Class物件資訊。這些類檔案的命名有嚴格的規則:外圍類的名字,加上”