第十章 內部類 內部類的作用、閉包、內部類繼承、覆蓋重寫內部類、區域性內部類、內部類識別符號
阿新 • • 發佈:2018-12-26
1.為什麼需要內部類
- 內部類繼承自某個類或者實現某個介面,內部類可以訪問建立它的外部類的物件或引數資源。
- 內部類能獨立的實現某個介面,無論外圍類是否實現了這個介面。
- 內部類使多重繼承的解決方案變得更加完善。
下面的程式碼體現內部類實現多重繼承,實現多個介面
從實現的觀點來看,上面的外部類實現多重繼承和內部類實現多重繼承並沒有什麼區別interface A{} interface B{} //實現兩個介面的單一類 class X implements A,B{} //實現兩個介面的存在內部類的類 class Y implements A{ //在內部類實現另一個B介面 B makeB(){ //返回一個內部類 return new B() {}; } } public class MultiInterfaces { static void takesA(A a){} static void takesB(B b){} public static void main(String[] args) { //宣告外部類 X x = new X(); Y y = new Y(); /** * 下面可以看到x和y都可以實現傳入takes.()方法中,而這是兩個介面 */ takesA(x); takesA(y); takesB(x); //獲取內部類並傳入takesB() takesB(y.makeB()); } }
- 上面是介面,所以實現多重繼承沒有問題,但如果要對抽象類或實際類實現多重繼承,就需要使用內部類來實現
class D{} abstract class E{} //一個外部類繼承了D就不能再繼承其他的類了 class Z extends D{ //內部類實現多重繼承 E makeE(){return new E(){};} } public class MultiImplementation { static void takesD(D d){} static void takesE(E e){} public static void main(String[] args) { Z z = new Z(); takesD(z); //獲取Z中的E內部類物件 takesE(z.makeE()); }
- 內部類可以有多個例項,每個例項都有自己的狀態資訊,並且與外圍類相互獨立。
- 在單個外圍類中,可以讓多個內部類以不同的方式實現同一個介面(多個內部類的實現)。
- 建立內部類的時刻並不依賴於外圍類物件的建立。【這句好像與前面矛盾,樓主也不解】
- 內部類沒有“is-a”關係,它就是一個實體。
2.閉包
- 閉包是指可以包含自由(未繫結到特定物件)變數的程式碼塊;這些變數不是在這個程式碼塊內或者任何全域性上下文中定義的,而是在定義程式碼塊的環境中定義(區域性變數)。閉包包含的自由變數來自於定義閉包的環境。
- java中的閉包是一個物件,它記錄了一些資訊,這些資訊來自於建立它的作用域。
- 通過閉包實現java中的回撥
在Android中,閉包回撥多用於Activity和其Fragment之間的傳參,當然adapter有時也會與Activity傳參使用回撥介面的方法。interface Incrementable{ void increment(); } //外圍類實現一個介面 class Callee1 implements Incrementable{ private int i = 0; //回撥方法 @Override public void increment() { i++; System.out.println(i); } } class MyIncrement{ //一個同上面介面中的方法相同的方法 public void increment(){System.out.println("Other operation");} static void f(MyIncrement mi){mi.increment();} } //繼承自上面的MyIncrement,因為MyIncrement中的方法與Incrementable介面中方法相同,實現回撥介面的功能就不可以用普通的方式實現了 class Callee2 extends MyIncrement{ private int i = 0; public void increment() { super.increment(); i++; System.out.println(i); } //一個內部類,實現了一個回撥介面,這是一個<span style="color:#FF0000;">閉包</span>,資訊來自於建立它的作用域 private class Closure implements Incrementable{ //回撥方法,也叫鉤子 @Override public void increment() { /** * 執行外部類的方法,這樣只要其他的物件獲取到這個內部類介面,就可以呼叫這個回撥方法,回撥方法裡面呼叫外部類的private引數, * 實現與外界的相通 */ Callee2.this.increment(); //使用一個private i ++; } } //返回一個回撥介面,與外界相聯絡 Incrementable getCallbackReference(){ return new Closure(); } } class Caller{ private Incrementable callbackReference; public Caller(Incrementable cbh) {callbackReference = cbh;} void go(){callbackReference.increment();} } public class Callbacks { public static void main(String[] args) { Callee1 c1 = new Callee1(); Callee2 c2 = new Callee2(); MyIncrement.f(c2); //傳入一個實現了回撥介面的物件 Caller caller1 = new Caller(c1); //得到Callee2內部類宣告的回撥介面,並傳入Caller物件,供Caller物件回撥介面中的方法 Caller caller2 = new Caller(c2.getCallbackReference()); //執行回撥 caller1.go(); caller1.go(); caller2.go(); } }
- 回撥的價值在於它的靈活性,可以在程式執行時動態的決定呼叫什麼方法。
3.內部類與繼承
- 整合過程中的問題在於:內部類的構造器必須連結到只想外圍類物件的引用,而到匯出類(子類)不存在可連線的預設物件引用。
要解決這個問題,必須使用特殊的語法class WithInner{ class Inner{} } //繼承一個內部類 public class InneritInner extends WithInner.Inner{ public InneritInner(WithInner wi) { /** * 構造期內必須引用所繼承的外圍類物件 * 編譯器才不會報錯 */ wi.super(); } public static void main(String[] args) { //呼叫的時候,總之內部類必須與其外圍類連結才能使用,即使繼承 WithInner wi = new WithInner(); InneritInner ii = new InneritInner(wi); } }
4.覆蓋(重寫)內部類,像重寫方法一樣
- 子類可不可以覆蓋父類中的內部類呢
class Egg{ private Yolk y; protected class Yolk{ public Yolk() {System.out.println("Egg.Yolk()");} } public Egg() { System.out.println("New Egg()"); //宣告一個內部類物件 y = new Yolk(); } } public class BigEgg extends Egg{ //由於父類中也有這個類,所以氣不氣到覆蓋作用呢 public class Yolk{ public Yolk() {System.out.println("BigEgg.Yolk()");} } public static void main(String[] args) { new BigEgg(); } } /** *輸出:未達到想要的情況,未出現覆蓋情況 *New Egg() *Egg.Yolk() */
- 當繼承某個外圍類的時候,內部類並沒有發生特別的變化。這兩個內部類是完全獨立的兩個實體,在各自的名稱空間內。
- 明確的繼承某個內部類,子類內部類繼承父類的內部類,構造器的執行過程是先執行外圍類的,再執行相關內部類的
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(); } } /** * Egg2.Yolk() //這兒很奇怪,其實是先初始化最終父類的class域內的變數,這個是Egg2中的private Yolk y = new Yolk(); * New Egg2() * Egg2.Yolk() * BigEgg2.Yolk() * BigEgg2.Yolk.f() * */
5.區域性內部類
- 在程式碼塊裡建立的內部類,比如在一個方法體內建立一個內部類
- 區域性內部類不能有訪問修飾詞(private 等),因為它不是外圍類的一部分。
- 可以訪問當前程式碼塊內的常量,以及此外圍類的所有成員。
區域性內部類的名字在方法外是不可見的,和匿名內部類沒有名字是一樣的,那為什麼要用區域性內部類??interface Counter{ int next(); } public class LocalInnerClass { private int count = 0; Counter getCounter(final String name){ //區域性內部類 class LocalCounter implements Counter{ //重寫構造器 public LocalCounter() { System.out.println("LocalCounter"); } @Override public int next() { System.out.println(name); return count ++; } } //返回此區域性內部類 return new LocalCounter(); } Counter getCounter2(final String name){ //匿名內部類 return new Counter() { //匿名內部類的高仿構造器 {System.out.println("Counter");} @Override public int next() { System.out.println(name); return count ++; } }; } public static void main(String[] args) { LocalInnerClass lic = new LocalInnerClass(); Counter c1 = lic.getCounter("Loc inner"), c2 = lic.getCounter2("Anonymous inner"); for (int i = 0; i < 5; i++) System.out.println(c1.next()); for (int i = 0; i < 5; i++) System.out.println(c2.next()); } }
唯一的理由是內部類需要一個已命名的構造器或者重寫構造器,而匿名內部類並不能實現構造器,它只能用於例項初始化。
另一個理由是需要不止一個該內部類的物件。
6.內部類識別符號
- 每個類都會產生一個.class檔案,其中包含了如何建立該類的物件的全部資訊。
- 內部類也必須生成一個.class檔案已包含他們的Class物件資訊。
- 類檔案的命名有嚴格的規則。
外圍類的名字,加上$,在加上內部類的名字。例如LocallnnerClass生成的.class檔案包括:
Counter.class
LocalInnerClass$1.class
LocalInnerClass$1LocalCounter.class
LocalInnerClass.class - 如果內部類是匿名的,編譯器會簡單的產生一個數字作為其識別符號。
如果內部類是巢狀在別的內部類之中,只需直接將它們的名字簡單加在其外圍類識別符號與$的後面
7.應用程式框架
- 應用程式框架——解決特定的問題的框架。
- 應用程式框架就是被設計用以解決某類特定問題的一個類或一組類。
- 要運用某個應用程式框架,通常是繼承一個或多個類,並覆蓋某些方法。
在覆蓋的方法中編寫程式碼定製應用程式提供的通用解決方案,以解決特定問題。——模板方法設計模式。
設計模式總是將變化的事物和不變的事物分離開來。其中模板方法是保持不變的事物,可覆蓋的方法就是變化的事物。
8.內部類和控制框架
- 控制框架是一類特殊的應用程式框架,用來解決響應事件的需求。
- 驅動系統:用來響應事件的系統。
public abstract class Event { private long eventTime; protected final long delayTime; public Event(long delayTimg){ this.delayTime = delayTimg; start(); } /** * 啟動計時器的方法,呼叫此方法可以重新啟動計時器,也能夠重複使用Event物件。 * 重複一個事件,就在action中呼叫start()方法 */ public void start(){ eventTime = System.nanoTime() + delayTime; } //何時可執行action()方法。覆蓋此方法,實現Event除基於事件以外的其他因素出發action方法 public boolean ready(){ return System.nanoTime() >= eventTime; } //動作執行方法 public abstract void action(); }
public class GreenHouseControls extends Controller{ private boolean light = false; //一個內部類 繼承Event事件類控制開燈 public class LightOn extends Event{ public LightOn(long delayTimg) {super(delayTimg);} @Override public void action() { light = true; } public String toString(){return "Light is on";} } //一個內部類 繼承Event事件類控制開燈 public class LightOff extends Event{ public LightOff(long delayTimg) {super(delayTimg);} @Override public void action() { light = false; } public String toString(){return "Light is off";} } }
public class GreenhouseController { public static void main(String[] args) { GreenHouseControls gc = new GreenHouseControls(); //新增一個事件 1秒後執行 gc.addEvent(gc.new LightOff(1000)); } }
總結:
- 內部類使得多重繼承更加的完善。