1. 程式人生 > >第十章 內部類 內部類的作用、閉包、內部類繼承、覆蓋重寫內部類、區域性內部類、內部類識別符號

第十章 內部類 內部類的作用、閉包、內部類繼承、覆蓋重寫內部類、區域性內部類、內部類識別符號

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());
    	}
    
    
  1. 內部類可以有多個例項,每個例項都有自己的狀態資訊,並且與外圍類相互獨立。
  2. 在單個外圍類中,可以讓多個內部類以不同的方式實現同一個介面(多個內部類的實現)。
  3. 建立內部類的時刻並不依賴於外圍類物件的建立。【這句好像與前面矛盾,樓主也不解】
  4. 內部類沒有“is-a”關係,它就是一個實體。

2.閉包

  • 閉包是指可以包含自由(未繫結到特定物件)變數的程式碼塊;這些變數不是在這個程式碼塊內或者任何全域性上下文中定義的,而是在定義程式碼塊的環境中定義(區域性變數)。閉包包含的自由變數來自於定義閉包的環境。
  • java中的閉包是一個物件,它記錄了一些資訊,這些資訊來自於建立它的作用域。
  • 通過閉包實現java中的回撥
    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();
    	}
    }
    
    在Android中,閉包回撥多用於Activity和其Fragment之間的傳參,當然adapter有時也會與Activity傳參使用回撥介面的方法。
  • 回撥的價值在於它的靈活性,可以在程式執行時動態的決定呼叫什麼方法。

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.區域性內部類

  • 在程式碼塊裡建立的內部類,比如在一個方法體內建立一個內部類
  1. 區域性內部類不能有訪問修飾詞(private 等),因為它不是外圍類的一部分。
  2. 可以訪問當前程式碼塊內的常量,以及此外圍類的所有成員。
  3. 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));
    	}
    }
    

總結:

  • 內部類使得多重繼承更加的完善。