Java內(nèi)部類總結(jié)
內(nèi)部類是指在一個(gè)外部類的內(nèi)部再定義一個(gè)類。內(nèi)部類作為外部類的一個(gè)成員,并且依附于外部類而存在的。內(nèi)部類可為靜態(tài),可用protected和private修飾(而外部類只能使用public和缺省的包訪問權(quán)限)。內(nèi)部類主要有以下幾類:成員內(nèi)部類、局部?jī)?nèi)部類、靜態(tài)內(nèi)部類、匿名內(nèi)部類
為什么需要內(nèi)部類? 典型的情況是,內(nèi)部類繼承自某個(gè)類或?qū)崿F(xiàn)某個(gè)接口,內(nèi)部類的代碼操作創(chuàng)建其的外圍類的對(duì)象。所以你可以認(rèn)為內(nèi)部類提供了某種進(jìn)入其外圍類的窗口。使用內(nèi)部類最吸引人的原因是: 每個(gè)內(nèi)部類都能獨(dú)立地繼承自一個(gè)(接口的)實(shí)現(xiàn),所以無論外圍類是否已經(jīng)繼承了某個(gè)(接口的)實(shí)現(xiàn),對(duì)于內(nèi)部類都沒有影響。如果沒有內(nèi)部類提供的可以繼承多個(gè)具體的或抽象的類的能力,一些設(shè)計(jì)與編程問題就很難解決。從這個(gè)角度看,內(nèi)部類使得多重繼承的解決方案變得完整。接口解決了部分問題,而內(nèi)部類有效地實(shí)現(xiàn)了“多重繼承”。 A:成員內(nèi)部類 作為外部類的一個(gè)成員存在,與外部類的屬性、方法并列。 publicclass Outer { privatestaticinti = 1; privateintj = 10; privateintk = 20; publicstaticvoidouter_f1() { } publicvoidouter_f2() { } // 成員內(nèi)部類中,不能定義靜態(tài)成員 // 成員內(nèi)部類中,可以訪問外部類的所有成員 class Inner { // static int inner_i = 100; //內(nèi)部類中不允許定義靜態(tài)變量 intj = 100; // 內(nèi)部類和外部類的實(shí)例變量可以共存 intinner_i = 1; void inner_f1() { System.out.println(i); //在內(nèi)部類中訪問內(nèi)部類自己的變量直接用變量名 System.out.println(j); //在內(nèi)部類中訪問內(nèi)部類自己的變量也可以用this.變量名 System.out.println(this.j); //在內(nèi)部類中訪問外部類中與內(nèi)部類同名的實(shí)例變量用外部類名.this.變量名 System.out.println(Outer.this.j); //如果內(nèi)部類中沒有與外部類同名的變量,則可以直接用變量名訪問外部類變量 System.out.println(k); outer_f1(); outer_f2(); } } //外部類的非靜態(tài)方法訪問成員內(nèi)部類 publicvoidouter_f3() { Inner inner = new Inner(); inner.inner_f1(); } // 外部類的靜態(tài)方法訪問成員內(nèi)部類,與在外部類外部訪問成員內(nèi)部類一樣 publicstaticvoidouter_f4() { //step1 建立外部類對(duì)象 Outer out = new Outer(); //step2 根據(jù)外部類對(duì)象建立內(nèi)部類對(duì)象 Inner inner = out.new Inner(); //step3 訪問內(nèi)部類的方法 inner.inner_f1(); } publicstaticvoid main(String[] args) { //outer_f4(); //該語句的輸出結(jié)果和下面三條語句的輸出結(jié)果一樣 //如果要直接創(chuàng)建內(nèi)部類的對(duì)象,不能想當(dāng)然地認(rèn)為只需加上外圍類Outer的名字, //就可以按照通常的樣子生成內(nèi)部類的對(duì)象,而是必須使用此外圍類的一個(gè)對(duì)象來 //創(chuàng)建其內(nèi)部類的一個(gè)對(duì)象: //Outer.Inner outin = out.new Inner() //因此,除非你已經(jīng)有了外圍類的一個(gè)對(duì)象,否則不可能生成內(nèi)部類的對(duì)象。因?yàn)榇?br> //內(nèi)部類的對(duì)象會(huì)悄悄地鏈接到創(chuàng)建它的外圍類的對(duì)象。如果你用的是靜態(tài)的內(nèi)部類, //那就不需要對(duì)其外圍類對(duì)象的引用。 Outer out = new Outer(); Outer.Inner outin = out.new Inner(); outin.inner_f1(); } } 注意:內(nèi)部類是一個(gè)編譯時(shí)的概念,一旦編譯成功,就會(huì)成為完全不同的兩類。對(duì)于一個(gè)名為outer的外部類和其內(nèi)部定義的名為inner的內(nèi)部類。編譯完成后出現(xiàn)outer.class和outer$inner.class兩類。 B:局部?jī)?nèi)部類 在方法中定義的內(nèi)部類稱為局部?jī)?nèi)部類。與局部變量類似,局部?jī)?nèi)部類不能有訪問說明符,因?yàn)樗皇峭鈬惖囊徊糠?,但是它可以訪問當(dāng)前代碼塊內(nèi)的常量,和此外圍類所有的成員。 publicclass Outer { privateints = 100; privateintout_i = 1; publicvoid f(finalint k) { finalint s = 200; int i = 1; finalint j = 10; //定義在方法內(nèi)部 class Inner { ints = 300; // 可以定義與外部類同名的變量 // static int m = 20; //不可以定義靜態(tài)變量 Inner(int k) { inner_f(k); } intinner_i = 100; voidinner_f(int k) { //如果內(nèi)部類沒有與外部類同名的變量,在內(nèi)部類中可以直接訪問外部類的實(shí)例變量 System.out.println(out_i); //可以訪問外部類的局部變量(即方法內(nèi)的變量),但是變量必須是final的 System.out.println(j); //System.out.println(i); //如果內(nèi)部類中有與外部類同名的變量,直接用變量名訪問的是內(nèi)部類的變量 System.out.println(s); //用this.變量名訪問的也是內(nèi)部類變量 System.out.println(this.s); //用外部類名.this.內(nèi)部類變量名訪問的是外部類變量 System.out.println(Outer.this.s); } } new Inner(k); } publicstaticvoid main(String[] args) { // 訪問局部?jī)?nèi)部類必須先有外部類對(duì)象 Outer out = new Outer(); out.f(3); } } C:靜態(tài)內(nèi)部類(嵌套類):(注意:前兩種內(nèi)部類與變量類似,所以可以對(duì)照參考變量) 如果你不需要內(nèi)部類對(duì)象與其外圍類對(duì)象之間有聯(lián)系,那你可以將內(nèi)部類聲明為static。這通常稱為嵌套類(nested class)。想要理解static應(yīng)用于內(nèi)部類時(shí)的含義,你就必須記住,普通的內(nèi)部類對(duì)象隱含地保存了一個(gè)引用,指向創(chuàng)建它的外圍類對(duì)象。然而,當(dāng)內(nèi)部類是static的時(shí),就不是這樣了。嵌套類意味著: 1. 要?jiǎng)?chuàng)建嵌套類的對(duì)象,并不需要其外圍類的對(duì)象。 2. 不能從嵌套類的對(duì)象中訪問非靜態(tài)的外圍類對(duì)象。 publicclass Outer { privatestaticinti = 1; privateintj = 10; publicstaticvoidouter_f1() { } publicvoidouter_f2() { } // 靜態(tài)內(nèi)部類可以用public,protected,private修飾 // 靜態(tài)內(nèi)部類中可以定義靜態(tài)或者非靜態(tài)的成員 staticclass Inner { staticintinner_i = 100; intinner_j = 200; staticvoidinner_f1() { //靜態(tài)內(nèi)部類只能訪問外部類的靜態(tài)成員(包括靜態(tài)變量和靜態(tài)方法) System.out.println("Outer.i" + i); outer_f1(); } voidinner_f2() { // 靜態(tài)內(nèi)部類不能訪問外部類的非靜態(tài)成員(包括非靜態(tài)變量和非靜態(tài)方法) // System.out.println("Outer.i"+j); // outer_f2(); } } publicvoidouter_f3() { // 外部類訪問內(nèi)部類的靜態(tài)成員:內(nèi)部類.靜態(tài)成員 System.out.println(Inner.inner_i); Inner.inner_f1(); // 外部類訪問內(nèi)部類的非靜態(tài)成員:實(shí)例化內(nèi)部類即可 Inner inner = new Inner(); inner.inner_f2(); } publicstaticvoid main(String[] args) { newOuter().outer_f3(); } } 生成一個(gè)靜態(tài)內(nèi)部類不需要外部類成員:這是靜態(tài)內(nèi)部類和成員內(nèi)部類的區(qū)別。靜態(tài)內(nèi)部類的對(duì)象可以直接生成:Outer.Inner in = new Outer.Inner(); 而不需要通過生成外部類對(duì)象來生成。這樣實(shí)際上使靜態(tài)內(nèi)部類成為了一個(gè)頂級(jí)類(正常情況下,你不能在接口內(nèi)部放置任何代碼,但嵌套類可以作為接口的一部分,因?yàn)樗莝tatic 的。只是將嵌套類置于接口的命名空間內(nèi),這并不違反接口的規(guī)則) D:匿名內(nèi)部類(from thinking in java 3th) 簡(jiǎn)單地說:匿名內(nèi)部類就是沒有名字的內(nèi)部類。什么情況下需要使用匿名內(nèi)部類?如果滿足下面的一些條件,使用匿名內(nèi)部類是比較合適的: ·只用到類的一個(gè)實(shí)例。 ·類在定義后馬上用到。 ·類非常?。⊿UN推薦是在4行代碼以下) ·給類命名并不會(huì)導(dǎo)致你的代碼更容易被理解。 在使用匿名內(nèi)部類時(shí),要記住以下幾個(gè)原則: ·匿名內(nèi)部類不能有構(gòu)造方法。 ·匿名內(nèi)部類不能定義任何靜態(tài)成員、方法和類。 ·匿名內(nèi)部類不能是public,protected,private,static。 ·只能創(chuàng)建匿名內(nèi)部類的一個(gè)實(shí)例。 ·一個(gè)匿名內(nèi)部類一定是在new的后面,用其隱含實(shí)現(xiàn)一個(gè)接口或?qū)崿F(xiàn)一個(gè)類。 ·因匿名內(nèi)部類為局部?jī)?nèi)部類,所以局部?jī)?nèi)部類的所有限制都對(duì)其生效。 下面的例子看起來有點(diǎn)奇怪: //在方法中返回一個(gè)匿名內(nèi)部類 public class Parcel6 { public Contents cont() { return new Contents() { private int i = 11; public int value() { return i; } }; // 在這里需要一個(gè)分號(hào) } public static void main(String[] args) { Parcel6 p = new Parcel6(); Contents c = p.cont(); } } cont()方法將下面兩個(gè)動(dòng)作合并在一起:返回值的生成,與表示這個(gè)返回值的類的定義!進(jìn)一步說,這個(gè)類是匿名的,它沒有名字。更糟的是,看起來是你正要?jiǎng)?chuàng)建一個(gè)Contents對(duì)象: return new Contents() 但是,在到達(dá)語句結(jié)束的分號(hào)之前,你卻說:“等一等,我想在這里插入一個(gè)類的定義”: return new Contents() { private int i = 11; public int value() { return i; } }; 這種奇怪的語法指的是:“創(chuàng)建一個(gè)繼承自Contents的匿名類的對(duì)象。”通過new 表達(dá)式返回的引用被自動(dòng)向上轉(zhuǎn)型為對(duì)Contents的引用。匿名內(nèi)部類的語法是下面例子的簡(jiǎn)略形式: class MyContents implements Contents { private int i = 11; public int value() { return i; } } return new MyContents(); 在這個(gè)匿名內(nèi)部類中,使用了缺省的構(gòu)造器來生成Contents。下面的代碼展示的是,如果你的基類需要一個(gè)有參數(shù)的構(gòu)造器,應(yīng)該怎么辦: public class Parcel7 { public Wrapping wrap(int x) { // Base constructor call: return new Wrapping(x) { // Pass constructor argument. public int value() { return super.value() * 47; } }; // Semicolon required } public static void main(String[] args) { Parcel7 p = new Parcel7(); Wrapping w = p.wrap(10); } } 只需簡(jiǎn)單地傳遞合適的參數(shù)給基類的構(gòu)造器即可,這里是將x 傳進(jìn)new Wrapping(x)。在匿名內(nèi)部類末尾的分號(hào),并不是用來標(biāo)記此內(nèi)部類結(jié)束(C++中是那樣)。實(shí)際上,它標(biāo)記的是表達(dá)式的結(jié)束,只不過這個(gè)表達(dá)式正巧包含了內(nèi)部類罷了。因此,這與別的地方使用的分號(hào)是一致的。 如果在匿名類中定義成員變量,你同樣能夠?qū)ζ鋱?zhí)行初始化操作: public class Parcel8 { // Argument must be final to use inside // anonymous inner class: public Destination dest(final String dest) { return new Destination() { private String label = dest; public String readLabel() { return label; } }; } public static void main(String[] args) { Parcel8 p = new Parcel8(); Destination d = p.dest("Tanzania"); } } 如果你有一個(gè)匿名內(nèi)部類,它要使用一個(gè)在它的外部定義的對(duì)象,編譯器會(huì)要求其參數(shù)引用是final 型的,就像dest()中的參數(shù)。如果你忘記了,會(huì)得到一個(gè)編譯期錯(cuò)誤信息。如果只是簡(jiǎn)單地給一個(gè)成員變量賦值,那么此例中的方法就可以了。但是,如果你想做一些類似構(gòu)造器的行為,該怎么辦呢?在匿名類中不可能有已命名的構(gòu)造器(因?yàn)樗緵]名字?。?,但通過實(shí)例初始化,你就能夠達(dá)到為匿名內(nèi)部類“制作”一個(gè)構(gòu)造器的效果。像這樣做: abstract class Base { public Base(int i) { System.out.println("Base constructor, i = " + i); } public abstract void f(); } public class AnonymousConstructor { public static Base getBase(int i) { return new Base(i) { { System.out.println("Inside instance initializer"); } public void f() { System.out.println("In anonymous f()"); } }; } public static void main(String[] args) { Base base = getBase(47); base.f(); } } 在此例中,不要求變量i 一定是final 的。因?yàn)閕 被傳遞給匿名類的基類的構(gòu)造器,它并不會(huì)在匿名類內(nèi)部被直接使用。下例是帶實(shí)例初始化的“parcel”形式。注意dest()的參數(shù)必須是final,因?yàn)樗鼈兪窃谀涿悆?nèi)被使用的。 public class Parcel9 { public Destinationdest(final String dest, final float price) { return new Destination() { private int cost; // Instance initialization for each object: { cost = Math.round(price); if(cost > 100) System.out.println("Over budget!"); } private String label = dest; public String readLabel() { return label; } }; } public static void main(String[] args) { Parcel9 p = new Parcel9(); Destination d = p.dest("Tanzania", 101.395F); } } 在實(shí)例初始化的部分,你可以看到有一段代碼,那原本是不能作為成員變量初始化的一部分而執(zhí)行的(就是if 語句)。所以對(duì)于匿名類而言,實(shí)例初始化的實(shí)際效果就是構(gòu)造器。當(dāng)然它受到了限制:你不能重載實(shí)例初始化,所以你只能有一個(gè)構(gòu)造器。 從多層嵌套類中訪問外部 一個(gè)內(nèi)部類被嵌套多少層并不重要,它能透明地訪問所有它所嵌入的外圍類的所有成員,如下所示: 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(); } } 可以看到在MNA.A.B中,調(diào)用方法g()和f()不需要任何條件(即使它們被定義為private)。這個(gè)例子同時(shí)展示了如何從不同的類里面創(chuàng)建多層嵌套的內(nèi)部類對(duì)象的基本語法。“.new”語法能產(chǎn)生正確的作用域,所以你不必在調(diào)用構(gòu)造器時(shí)限定類名。 內(nèi)部類的重載問題 如果你創(chuàng)建了一個(gè)內(nèi)部類,然后繼承其外圍類并重新定義此內(nèi)部類時(shí),會(huì)發(fā)生什么呢?也就是說,內(nèi)部類可以被重載嗎?這看起來似乎是個(gè)很有用的點(diǎn)子,但是“重載”內(nèi)部類就好像它是外圍類的一個(gè)方法,其實(shí)并不起什么作用: class Egg { private Yolk y; protectedclass Yolk { public Yolk() { System.out.println("Egg.Yolk()"); } } public Egg() { System.out.println("New Egg()"); y = new Yolk(); } } publicclass BigEgg extends Egg { publicclass Yolk { public Yolk() { System.out.println("BigEgg.Yolk()"); } } publicstaticvoid main(String[] args) { new BigEgg(); } } 輸出結(jié)果為: New Egg() Egg.Yolk() 缺省的構(gòu)造器是編譯器自動(dòng)生成的,這里是調(diào)用基類的缺省構(gòu)造器。你可能認(rèn)為既然創(chuàng)建了BigEgg 的對(duì)象,那么所使用的應(yīng)該是被“重載”過的Yolk,但你可以從輸出中看到實(shí)際情況并不是這樣的。 這個(gè)例子說明,當(dāng)你繼承了某個(gè)外圍類的時(shí)候,內(nèi)部類并沒有發(fā)生什么特別神奇的變化。這兩個(gè)內(nèi)部類是完全獨(dú)立的兩個(gè)實(shí)體,各自在自己的命名空間內(nèi)。當(dāng)然,明確地繼承某個(gè)內(nèi)部類也是可以的: class Egg2 { protected class Yolk { public Yolk() { System.out.println("Egg2.Yolk()"); } public void f() { System.out.println("Egg2.Yolk.f()"); } } private Yolk y = new Yolk(); public Egg2() { System.out.println("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.println("BigEgg2.Yolk()"); } public void f() { System.out.println("BigEgg2.Yolk.f()"); } } public BigEgg2() { insertYolk(new Yolk()); } public static void main(String[] args) { Egg2 e2 = new BigEgg2(); e2.g(); } } 輸出結(jié)果為: Egg2.Yolk() New Egg2() Egg2.Yolk() BigEgg2.Yolk() BigEgg2.Yolk.f() 現(xiàn)在BigEgg2.Yolk 通過extends Egg2.Yolk 明確地繼承了此內(nèi)部類,并且重載了其中的方法。Egg2 的insertYolk()方法使得BigEgg2 將它自己的Yolk 對(duì)象向上轉(zhuǎn)型,然后傳遞給引用y。所以當(dāng)g()調(diào)用y.f()時(shí),重載后的新版的f()被執(zhí)行。第二次調(diào)用Egg2.Yolk()是BigEgg2.Yolk 的構(gòu)造器調(diào)用了其基類的構(gòu)造器??梢钥吹皆谡{(diào)用g()的時(shí)候,新版的f()被調(diào)用了。 內(nèi)部類的繼承問題(thinking in java 3th p294) 因?yàn)閮?nèi)部類的構(gòu)造器要用到其外圍類對(duì)象的引用,所以在你繼承一個(gè)內(nèi)部類的時(shí)候,事情變得有點(diǎn)復(fù)雜。問題在于,那個(gè)“秘密的”外圍類對(duì)象的引用必須被初始化,而在被繼承的類中并不存在要聯(lián)接的缺省對(duì)象。要解決這個(gè)問題,需使用專門的語法來明確說清它們之間的關(guān)聯(lián): class WithInner { class Inner { Inner(){ System.out.println("this is a constructor in WithInner.Inner"); }; } } public class InheritInner extends WithInner.Inner { // ! InheritInner() {} // Won't compile InheritInner(WithInner wi) { wi.super(); System.out.println("this is a constructor in InheritInner"); } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } } 輸出結(jié)果為: this is a constructor in WithInner.Inner this is a constructor in InheritInner 可以看到,InheritInner 只繼承自內(nèi)部類,而不是外圍類。但是當(dāng)要生成一個(gè)構(gòu)造器時(shí),缺省的構(gòu)造器并不算好,而且你不能只是傳遞一個(gè)指向外圍類對(duì)象的引用。此外,你必須在構(gòu)造器內(nèi)使用如下語法: enclosingClassReference.super(); 這樣才提供了必要的引用,然后程序才能編譯通過。 |
|