Think in Java第四版 讀書筆記4第九章第十章

第九章 抽象類與接口

9.1抽象類和抽象接口

抽象類可以有具體實現的方法(並不是所有方法都是abstract的)(比如這樣 abstract void test3();)
子類繼承抽象類要麼要實現(覆蓋)抽象類的abstract方法,要麼子類也要聲明爲抽象類
具有抽象方法的類必須是抽象類
抽象類無法實例化(因爲它具有未實現的方法)
可以聲明一個沒有抽象方法的抽象類(這樣做可以避免該類實例化)
在這裏插入圖片描述
代碼比較簡單就略過了
抽象類使得我們對現實事物的抽象更加顯而易見,更加明確。
一個例子

public abstract class Super {
	abstract void print();
	public Super() {
		print();
	}

}
public class Sub extends Super{
	int x = 20;

	void print() {
		System.out.println(x);
	}

}
public class Test {

	public static void main(String[] args) {
		Sub super1 = new Sub();
		super1.print();
	}

}

輸出

0
20

這道題目和上一章的例子類似不再贅述

9.2接口

接口是一個極其嚴格的抽象類,所有方法必須沒有實現。
接口可以變相實現多重繼承(一個類可以實現多個接口 但是隻能繼承一個父類)
接口中所有方法默認是public的(即使沒有寫),實現類實現時會自動加上public字段(繼承父類或實現接口,被覆蓋方法的訪問權限只能擴大,不能縮小,即父類或接口中的方法修飾符是public,子類或實現類只可以是public,不能是private的protected的,否則報錯Cannot reduce the visibility of the inherited method from 父類或接口)

9.3完全解耦

前面的章節我們提到繼承比較笨重,這裏也會體現出來。
假設有一個方法操作的是一個類,那麼我們只能傳入該類及其子類的實例,而如果該方法操作的是一個接口類型,那麼只要傳入的實例實現了該接口,就可以起作用。接口比繼承更靈活
書中講述了一個例子,例子使用繼承+多態實現了策略模式的效果
之後的濾波器的出現,證明了繼承的侷限性,解決方案是將Processor抽象爲接口而不是類
在無法修改類時(類在類庫中),可以通過適配器模式,增加一個適配器,讓新的類能適配原來的接口。

9.4 Java的“多重繼承”

通過implements多個接口實現“多重繼承”
使用接口的原因
1.使用接口類型的方法,進行向上轉型可以接受多種類型的參數
2.防止程序員創建接口對象(無法實例化抽象類 同樣的,無法實例化接口)
練習
創建一個接口,該接口繼承了另外兩個接口(接口可以extends不止一個東西,接口不能implements接口,只能extends接口)然後從後面兩個接口多重繼承第三個接口。

interface CanDo {
	void doIt();
}
interface CanDoFaster extends CanDo {
	void doFaster();
}
interface CanDoMore extends CanDo {
	void doMore();
}
interface CanDoMost extends CanDoMore, CanDoFaster {
	void doMost();
}
class Doer implements CanDoMost {
	public void doIt() {}
	public void doMore() {}
	public void doFaster() {}
	public void doMost() {}
}
public class Test {
	public static void main(String[] args) {
		Doer d = new Doer();
		((CanDoMore)d).doMore();
		((CanDoFaster)d).doFaster();
		((CanDo)d).doIt();	
	}
}

這就是傳說中的菱形繼承(僅限於接口的繼承)
在這裏插入圖片描述

9.5通過繼承擴展接口

接口新增的方法有2,第一直接在接口中新增方法,第二通過繼承其他接口獲取新的方法。

9.5.1組合接口時方法名衝突

例子

public interface interface1 {
	void method1();

}
public interface interface2 {
	void method1();
}
public class Test implements interface2,interface1{
	public void method1() {
		// TODO Auto-generated method stub
		
	}
}

這樣寫沒有問題,因爲interface1 interface2中的method1方法完全相同。
但是如果把interface1改成這樣(修改了返回值類型)

public interface interface1 {
	int method1();
}

那麼我們在實現類test無論怎麼寫都會出現問題,不管我們將返回值改爲什麼類型總和interface1或interface2其中一個衝突。
因此,最明智的方式是新增方法或者繼承接口時最好不要出現同名方法(即使程序允許),否則可能引起混亂

9.6適配接口

舉了一個實現Readable例子
然後使用繼承+實現接口的方法讓一個類既是RandomDoubles的又可以作爲Scanner的參數(Readable的)

9.7 接口中的域

由於接口中的域默認是final static的(也是public的),在SE 5 之前,這是產生enum類型的唯一方式,所以,以前的代碼可能是這麼寫的
在這裏插入圖片描述
這些域不是接口的一部分 存在靜態存儲區域。

9.8 嵌套接口

package suround;

import suround.A.E;

class A {
	interface B {
		void f();
	}

	public class BImp implements B {
		public void f() {
		}
	}

	private class BImp2 implements B {
		public void f() {
		}
	}

	interface C {
		void f();
	}

	class CImp implements C {
		public void f() {
		}
	}

	private class CImp2 implements C {
		public void f() {
		}
	}

	//接口也可以被實現爲private的 但限於類內部
	private interface D {
		void f();
	}

	//實現D接口的private類
	private class DImp implements D {
		public void f() {
		}
	}

	//實現D接口的public類
	private class DImp2 implements D {
		public void f() {
		}
	}

	public D getD() {
		return new DImp2();
	}

	private D dRef;

	public void receiveD(D d) {
		dRef = d;
		dRef.f();
	}

	//接口嵌套接口 G接口必須是public 的,這是interface對內部的性質
	interface E {
		interface G {
			void f();
		}

		// public 是多餘的,默認就是public,如果寫出private則報錯
		public interface H {
			void f();
		}

		void g();
	}

}

public class NestingInterfaces {
	public class BImp implements A.B {
		public void f() {
		}
	}

	class CImp implements A.C {
		public void f() {
		}
	}

	// 無法實現private的接口
	// class DImp implements A.D{
	// public void f() {
	// }
	// }

	
	//實現E接口只要實現E,不需要實現E內部的接口
	class EImp implements E {
		public void g() {
		}
	}
	
	class EGImp implements E.G {
		public void f() {
		}
	}
	
	class EImp2 implements E {
		public void g() {
		}
		class EG implements E.G{
			public void f() {
			}
		}
	}

	public static void main(String[] args) {
		A a = new A();
		//A.D class A的D接口爲private 不可訪問
		
		//A.DImp 也無法訪問
		
		//getD返回DImp2對象 它是private 的無法調用其方法
		//a.getD().f();
		
		//只有A對象內部才能調用DImp2 的 private方法
		A a2 = new A();
		a2.receiveD(a.getD());
	}

}

9.9 接口與工廠

實例
在這裏插入圖片描述
在這裏插入圖片描述
工廠模式的作用:利用向上轉型來實現一套代碼可以適用於不同類型的對象,調用不同類型對象的方法。將實際對象的實現隱藏(封裝)起來,使用者只能看到工廠和工廠方法

總結

9.1 抽象類簡介
9.2 接口–嚴格的抽象類
9.3 接口的目的之一— 利用向上轉型進行完全解耦
9.4 接口的目的之二—實現多重繼承的思想
9.5 接口extends接口
9.5.1 實現或者繼承接口時的命名衝突問題(儘量通過人爲避免)
9.6 當接口不能直接使用 可以利用適配器來適配接口
9.7 接口中的域默認是public static final的
9.8 接口嵌套以及一些特性
9.9 接口與工廠模式(可以實現代碼複用)

第十章 內部類

10.1 創建內部類

內部類常見用法示例

public class Parcel1 {
	class Contents{//內部類1
		private int i = 11;
		public int value() {
			return i;
		}
	}
	class Destination{//內部類2
		private String labelString;
		public Destination(String whereTo) {
			labelString = whereTo;
		}
		String readLabel(){
			return labelString;
		};
	}
	
	public void ship(String dest) {
		Contents contents = new Contents();//外部類方法中進行內部類實例化
		Destination destination = new Destination(dest);//外部類方法中進行內部類實例化
		System.out.println(destination.readLabel());//外部類方法 使用內部類實例調用內部類方法
	}
	
	public static void main(String [] args){
		Parcel1 parcel1 = new Parcel1();
		parcel1.ship("Tasmania");
	}
}

使用外部類對象獲取內部類實例

public class Parcel2 {
	class Contents{//inner class1
		private int i = 11;
		public int value() {
			return i;
		}
	}
	class Destination{//inner class2
		private String labelString;
		public Destination(String whereTo) {
			labelString = whereTo;
		}
		String readLabel(){
			return labelString;
		};
	}
	
	public Destination to(String s){//返回內部類實例
		return new Destination(s);
	}
	
	public Contents contents(){//返回內部類實例
		return new Contents();
	}
	
	public void ship(String dest) {
		Contents contents = contents();//outer class方法中調用inner class方法進行inner class實例化
		Destination destination = to(dest);//outer class方法中用inner class方法進行inner class實例化
		System.out.println(destination.readLabel());//outer class方法 使用inner class實例調用inner class方法
	}
	
	public static void main(String [] args){
		Parcel2 parcel2 = new Parcel2();
		parcel2.ship("Tasmania");
		
		Parcel2 qParcel2 = new Parcel2();
		Contents contents = qParcel2.contents();//使用outer class調用inner class方法獲取inner class實例
		Destination destination = qParcel2.to("Borneo");//使用outer class調用inner class方法獲取inner class實例
		Contents contents1 = qParcel2.new Contents();//也可以這樣創建實例
	}
}

10.2 鏈接到外部類

內部類對象隱式持有外部類對象的引用,因此內部類對象可以訪問外部類對象的所有方法和域(包括private類型的)。

public interface Selector {
	boolean end();
	Object current();
	void next();
}

public class Sequence {
	private Object[] itemsObjects;
	private int next = 0;

	public Sequence(int size) {
		itemsObjects = new Object[size];
	}

	public void add(Object x) {
		if (next < itemsObjects.length) {
			itemsObjects[next++] = x;
		}
	}

	private class SequenceSelector implements Selector {
		private int i = 0;

		public boolean end() {
			return i == itemsObjects.length;// inner class 訪問outer class私有變量
		}

		public Object current() {
			return itemsObjects[i];
		}

		public void next() {
			if (i < itemsObjects.length) {
				i++;
			}
		}
	}

	public Selector selector() {
		return new SequenceSelector();
	}

	public static void main(String[] args) {
		Sequence sequence = new Sequence(10);
		for (int i = 0; i < 10; i++) {
			sequence.add(Integer.toString(i));
		}
		Selector selector = sequence.selector();
		while (!selector.end()) {
			System.out.print(selector.current() + " ");
			selector.next();
		}
	}

}

輸出

0 1 2 3 4 5 6 7 8 9 

這是一個典型的迭代器模式。
在這裏插入圖片描述

10.3 使用.this 與.new

一句話:內部類返回外部類對象可以使用"外部類類名.this" 外部類返回內部類對象可以使用"外部類.new 內部類類名",示例如下
.this的使用

public class DotThis {
	void f(){
		System.out.println("DotThis.f()");
	}
	public class Inner{
		public DotThis outer(){
			return DotThis.this;//內部類返回外部類對象
			//單獨寫this表示內部類的this
		}
	}
	
	public Inner inner(){
		return new Inner();
	}

	public static void main(String[] args) {
		DotThis dt = new DotThis();
		DotThis.Inner dti = dt.inner();
		dti.outer().f();
	}
}

.new 的使用

public class Parcel3 {
	private int zz;
	int xl;
	class Contents{//inner class1
		private int i = 11;
		public int value() {
			return i;
		}
	}
	class Destination{//inner class2
		private String labelString;
		public Destination(String whereTo) {
			labelString = whereTo;
		}
		String readLabel(){
			return labelString;
		};
	}
	
	public static void main(String [] args){
		Parcel3 p = new Parcel3();
		Contents contents = p.new Contents();//使用外部類對象獲取內部類對象
		Destination destination = p.new Destination("Ta");//使用外部類對象獲取內部類對象
	}
}

正如我們前面說的,創建內部類對象時,內部類對象默認持有外部類對象的引用,因此,內部類對象不能單獨存在,必須使用外部類對象.new 的方式來創建內部類對象(有一個例外就是創建的內部類是靜態內部類,它不需要對外部類對象的引用)
更簡單的一個.new的例子

public class Outer {
	class Inner { 
		Inner() { System.out.println("Outer.Inner()"); } 
	}
}
public class OtherOuter {
	public static void main(String[] args) {
		// must first create outer class object:
		Outer o = new Outer();
		// then create inner class object:
		Inner oi = o.new Inner();			
	}
}

10.4 內部類與向上轉型

內部類的一個作用是隱藏實現
一個私有內部類實現了什麼接口,對外部調用者來說完全不可見,也不可以創建對象
示例

public interface Contents {
	int value();
}
public interface Destination {
	String readLabel();
}

public class Parcel4 {
	private class PContents implements Contents {//私有內部類
		private int i = 11;

		public int value() {
			return i;
		}
	}
	
	protected class PDestination implements Destination{//protected內部類
		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 parcel4 = new Parcel4();
		Contents c = parcel4.contents();
		Destination d = parcel4.destination("Test");
		//protected 權限在同一個包裏可以訪問
		PDestination destination = (PDestination) parcel4.destination("Test");
		//PContents 是private的無法訪問
		//PContents co;
		//Destination destination = parcel4.new PDestination("a");
	}

}

如果是將上述程序作爲第三方包使用,客戶端只能訪問到Destination和Contents 無法訪問到私有的Contents 和protected的PDestination,也無法進行向下轉型,這樣就達成了內部類的實現細節隱藏,客戶端只能調用公共接口的的方法 而不能調用PDestination和Contents裏面的其他方法。
練習題證明了
1.protected的內部類實現的接口,在繼承關係中,可以實現向下轉型。
2.內部類可以修改外部類域 調用外部類的方法
3.內部類和外部類可以相互訪問各自的private方法和變量

10.5 在方法和作用域內的內部類(局部內部類,只在限定範圍有效)

1定義在方法中

public interface Destination {
	String readLabel();
}
//定義在方法中的內部類(局部內部類)
public class Parcel5 {
	//PDestination是destination方法的一部分,在destination方法之外無法訪問PDestination(隱藏了PDestination)
	public Destination destination(String s){
		//只要不在destination方法中,可以在其他地方添加新的PDestination類而且不會有命名衝突,但是不推薦,容易產生歧義
		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("ABC");
	}
}

2定義在方法內部的作用域

//任意作用域嵌入內部類 同樣可以達到隱藏實現的目的
public class Parcel6 {
	private void interalTracking(boolean b){
		if (b) {
			class TrackingSlip{
				private String id;
				TrackingSlip(String id) {
					this.id = id;
				}
				String getSlip(){
					return id;
				}
			}
			//TrackingSlip的使用範圍僅限於if分支結束前,TrackingSlip定義之後
			//但是不管進不進if的case TrackingSlip都會參與編譯(而不是進入之後才編譯)
			TrackingSlip ts = new TrackingSlip("slip");
			String string = ts.getSlip();
			System.out.println(string);
		}
	}
	
	public static void main(String[] args) {
		Parcel6 p = new Parcel6();
		p.interalTracking(true);
	}
}

10.6 匿名內部類

1.匿名內部類簡單示例

public interface Contents {
	int value();
}
public class Parcel7 {
	public Contents contents(){
		return new Contents() {//返回時才創建一個繼承自Contents的匿名對象
			private int i = 11;
			public int value() {
				return i;
			}
		};//注意分號不可少
	}
	
	public static void main(String[] args) {
		Parcel7 p = new Parcel7();
		Contents contents = p.contents();
	}
}

2.匿名內部類的實際效果示例

public class Parcel7b {//Parcel7 是 Parcel7b的簡化形式
	public Contents contents() {
		return new MyContents();
	}

	class MyContents implements Contents {
		private int i = 11;

		public int value() {
			return 0;
		}
	}

	public static void main(String[] args) {
		Parcel7b p = new Parcel7b();
		Contents contents = p.contents();
	}
}

3.創建帶參構造方法的匿名內部類示例

public class Parcel8 {// 創建帶參構造方法的匿名內部類
	public class Wrapping {// 普通內部類
		private int i;

		Wrapping(int x) {
			i = x;
		}

		public int value() {
			return i;
		}

	}

	public Wrapping wrapping(int x) {
		return new Wrapping(x) {
			@Override
			public int value() {
				return super.value() * 47;
			}
		};
	}

	public static void main(String[] args) {
		Parcel8 p = new Parcel8();
		Wrapping wrapping = p.wrapping(1);
		System.out.println(wrapping.value());
	}

}

4.匿名內部類定義字段時同時進行初始化

public interface Destination {
	String readLabel();
}
public class Parcel9 {
	public Destination destination(final String dest){//匿名內部類的參數需要是final的
		return new Destination() {
			private String label = dest;//匿名內部類定義字段時同時進行初始化
			public String readLabel() {
				return label;
			}
		};
	}
	
	public static void main(String[] args) {
		Parcel9 p = new Parcel9();
		Destination d = p.destination("ABC");
	}
}

5.爲匿名內部類創建構造器示例
匿名內部類本來沒有名字,因此要有構造器本來是不可能的,但是,可以通過使用new 類名(實例化)的方式創建一個抽象類(需要實現抽象類內部細節)來達到效果

public 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){//i沒有在內部類直接使用,所以可以不用定義爲final的
		return new Base(i) {//匿名內部類的含參構造方法 如果父類用於多個參數,此處可以傳遞多個參數
			{
				System.out.println("Inside instance initializer ");
			}
			@Override
			public void f() {
				System.out.println("In anonymous f()");
			}
		};
	}

	public static void main(String[] args) {
		//先調用基類構造方法
		//調用匿名內部類靜態代碼塊代碼
		Base base = getBase(47);
		//調用f方法
		base.f();
	}

}

6.匿名內部類的侷限

public interface Destination {
	String readLabel();
}
public class Parcel10 {
	public Destination destination(final String dest, final float price) {// 匿名內部類直接使用的參數需要是final的
		return new Destination() {
			private int cost;
			{
				// 靜態代碼塊初始化
				cost = Math.round(price);
				if (price > 0) {
					System.out.println("cost initialize cost=" + cost);
				}
			}
			private String label = dest;// 匿名內部類定義字段時同時進行初始化

			public String readLabel() {
				return label;
			}
		};
	}

	public static void main(String[] args) {
		Parcel10 p = new Parcel10();
		Destination d = p.destination("ABC", 101);
	}
}
//匿名內部類可以實現接口 也可以擴展類 但是不能同時繼承和實現
//實現接口也只能實現一個接口

10.6 內部類與工廠模式

public interface Game {
	boolean move();
}
public interface GameFactory {
	Game getGame();
}

Game實現類

public class Checkers implements Game{
	private Checkers(){}
	private int moves = 0;
	private static final int MOVES = 3;

	public boolean move() {
		System.out.println("CHeckers move "+ moves);
		return ++moves != MOVES;
	}
	
	public static GameFactory factory = new GameFactory() {//匿名內部類
		
		public Game getGame() {
			return new Checkers();
		}
	};

}
public class Chess implements Game{
	private Chess(){}
	private int moves = 0;
	private static final int MOVES = 4;

	public boolean move() {
		System.out.println("Chess move "+ moves);
		return ++moves != MOVES;
	}
	
	public static GameFactory factory = new GameFactory() {//匿名內部類
		
		public Game getGame() {
			return new Chess();
		}
	};

}

每種game內都有一個匿名內部類用於返回GameFactory示例
測試類

public class Games {
	public static void playGame(GameFactory factory) {
		Game s = factory.getGame();
		while (s.move()) {
		}
	}
	public static void main(String [] args){
		playGame(Checkers.factory);
		playGame(Chess.factory);
	}

}

10.7嵌套類

什麼是嵌套類:書中的定義是static的內部類就是嵌套類,這似乎與網絡和我的理解有出入。
嵌套類與普通內部類的區別(個人理解應該是靜態內部類和非靜態內部類的區別):
1.靜態內部類創建對象時不需要外圍類的對象
2.不能從靜態內部類對象中訪問非靜態的外圍類對象
3.非靜態內部類需要this關鍵字來訪問外圍類 而靜態內部類不需要
一個靜態內部類的示例

public interface Contents {
	int value();
}
public interface Destination {
	String readLabel();
}
public class Parcel11 {
	private static class ParcelContents implements Contents {//靜態內部類1
		private int i = 11;

		public int value() {
			return i;
		}
	}

	private static class ParcelDestination implements Destination {//靜態內部類2
		private String label;

		public ParcelDestination(String whereTo) {
			label = whereTo;
		}

		public String readLabel() {
			return label;
		}

		public static void f() {
		};

		static int x = 10;

		static class AnotherInner {//靜態內部類2的內部類
			public static void f() {
			}

			static int x = 10;
		}
	}

	public static Destination destination(String s) {
		return new ParcelDestination(s);
	}

	public static Contents contents() {
		return new ParcelContents();
	}

	public static void main(String[] args) {
		Parcel11 p = new Parcel11();
		Destination d = p.destination("ABC");
	}
}

10.7.0編譯時的內部類帶有$符號,這個特點在很多內存泄漏的工具裏面有用

public class Ex19 {
	Ex19() { System.out.println("Ex19()"); }
	private class Ex19Inner {
		Ex19Inner() { System.out.println("Ex19Inner()"); } 
		private class Ex19InnerInner {
			Ex19InnerInner() {
				System.out.println("Ex19InnerInner()");
			}
		}
	}
	private static class Ex19Nested {
		Ex19Nested() { System.out.println("Ex19Nested()"); }
		private static class Ex19NestedNested {
			Ex19NestedNested() { 
			System.out.println("Ex19NestedNested()"); 
			}
		}
	}	
	public static void main(String[] args) {
		Ex19Nested en = new Ex19Nested();
		Ex19Nested.Ex19NestedNested enn = new Ex19Nested.Ex19NestedNested();
		Ex19 e19 = new Ex19();
		Ex19.Ex19Inner ei = e19.new Ex19Inner();
		Ex19.Ex19Inner.Ex19InnerInner eii = ei.new Ex19InnerInner();
	}
}

/* compiler produces:
* Ex19$Ex19Inner$Ex19InnerInner.class
* Ex19$Ex19Inner.class
* Ex19$Ex19Nested$Ex19NestedNested.class
* Ex19$Ex19Nested.class
* Ex19.class
*/

10.7.1 接口內部的類

//通常 不能再接口放置任何代碼,但是靜態內部類例外,將靜態內部類放置在接口的命名空間不違法接口的規則
public interface ClassInInterface {
	void howdy();
	class Test implements ClassInInterface{//該類默認擁有接口內部的屬性,即是public static的
		public void howdy() {
			System.out.println("Howdy");
		}
		
		public static void main(String [] args) {
			new Test().howdy();
		}
	}

創建公共代碼使得每個實現某一接口的任何類都能複用,接口的內部類可以實現這個功能
比如下面這個例子Nested 可以被任何繼承了In接口的類使用

interface In {
	class Nested {
		Nested() { System.out.println("Nested()"); }
		public void hi() { System.out.println("hi"); }		
	}
}

public class Ex20 implements In {
	public static void main(String[] args) {
		In.Nested in = new In.Nested();
		in.hi();				
	}
}

內部類與外部類(接口)聯動的例子

interface In {
	String f();
	String g(); 
	class Nested {
		static void testIn(In i) { 
			System.out.println(i.f() + i.g());
		}		
	}		
}

public class Ex21 implements In {
	public String f() { return "hello "; }
	public String g() { return "friend"; }
	public static void main(String[] args) {
		Ex21 x = new Ex21();
		In.Nested.testIn(x);						
	}
}

10.7.2 從多層嵌套類中訪問外部類的成員

無論嵌套多少層,內部類可以訪問所有外圍類的成員

public class MNA {
	private void f() {
	}
	
	class A{
		private void g(){}
		public class B{
			void h(){
				g();//訪問上層private方法
				f();//訪問上上層private方法
			}
		}
	}

}
import inner10dot7dot2.MNA.A;
import inner10dot7dot2.MNA.A.B;

public class TestMultiNestingAccess {

	public static void main(String[] args) {
		MNA mna = new MNA();
		A mnaa = mna.new A();
		B mnaab = mnaa.new B();
		mnaab.h();
		//mnaab.g();//訪問失敗
		//內部類訪問外部類方法 不是內部類實例訪問外部類方法,而是在方法內部可以訪問
	}
}

10.8 爲什麼需要內部類

如果實現兩個接口,有幾種方式來實現呢?

public interface A {

}
public interface B {

}

方式一 直接實現

public class TestImplement implements A,B{

}

方式二 通過內部類實現

public class TestInner implements A{
	B makeB(){
		return new B() {
		};
	}

}

測試類

public class Test {
	static void takeA(A a){}
	static void takeB(B b){}

	public static void main(String[] args) {
		TestImplement testImplement = new TestImplement();
		takeA(testImplement);
		takeB(testImplement);
		
		TestInner testInner = new TestInner();
		takeA(testInner);
		takeB(testInner.makeB());
	}

}

既然都可以實現,那麼內部類有什麼特別的呢?
上面可能看不出區別,但是如果不是實現接口而是繼承兩個不相關的類呢?由於java禁止多重繼承的關係,我們無法讓一個類繼承兩個類。因此我們只能通過內部類來實現這種多重繼承。這是內部類的最大優勢之一。
除了多重繼承,內部類還有以下特性
1.內部類可以有多個實例,每個實例之間相互獨立,與外部類信息也獨立
2.多個內部類可以以不同的形式實現或繼承接口和類
3.創建內部類對象的時刻不依賴外部類對象的創建(沒明白。。)
4.內部類是一個獨立的實體(內部類只能繼承或實現一個類或接口,返回的內部類對象就是這個接口或者類對象)

10.8.1 閉包與回調

個人理解通過內部類可以返回一個封閉的對象調用它的方法,實現者對內部類的控制方便
上例子

//接口定義
public interface Incrementable {
    void increment();
}

//實現Incrementable的第一種方式 直接通過類實現接口
public class Callee1 implements Incrementable{
	private int i = 0;

	public void increment() {
		i++;
		System.out.println("direct implements "+i);
	}
}
public class MyIncrement {
	void increment(){
		System.out.println("Other operation");
	}
	static void f(MyIncrement mi){
		mi.increment();
	}
}
class Callee2 extends MyIncrement {
	private int i = 0;

	// Callee2繼承了類MyIncrement,所以覆寫的是MyIncrement的increment方法,
	// 如果要實現接口Incrementable的increment方法,只能通過內部類獨立地實現。
	public void increment() {
		super.increment();
		i++;
		System.out.println("by inner class "+i);
	}

	//實現Incrementable的第二種方式-- 內部類實現Incrementable 這是個閉包
	private class Closure implements Incrementable {

		public void increment() {
			Callee2.this.increment();
		}
		
	}
	
	//通過方法返回內部類對象 用於調用內部類方法,通過內部類提供的閉包功能,完成回調(callback)的功能,而不是通過指針實現回調。
	// Closure是private,所以外部只能通過下面的方法得到接口的引用,也就限制了外部可以做的事情。
	Incrementable getCallbackReference(){
		return new Closure();
	}
}
//作用 獲取Incrementable引用以調用其方法
class Caller {
	private Incrementable callbackReference;

	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);//調用靜態方法 c2.i+1
		Caller caller1 = new Caller(c1);
		Caller caller2 = new Caller(c2.getCallbackReference());
		caller1.go();//c1.i+1
		caller1.go();//c1.i+1
		caller2.go();//c2.i+1
		caller2.go();//c2.i+1
	}
}

輸出

Other operation
by inner class 1
direct implements 1
direct implements 2
Other operation
by inner class 2
Other operation
by inner class 3

可以看到第二種實現中繼承的父類MyIncrement已經有了increment方法,此時可以通過內部類實現Incrementable的

10.8.2 內部類與控制框架(沒看明白他想表達什麼,略過)

10.9 內部類的繼承

由於內部類的創建依賴於外部類 因此在創建內部類時,指向外部類的引用必須初始化。

public class WithInner {
	class Inner{}
}
public class InheritInner extends WithInner.Inner{
	//默認構造方法報錯 因爲需要一個外部類的實例 (No enclosing instance of type WithInner is available due to some intermediate constructor invocation)
	//public InheritInner() {}
	
	public InheritInner(WithInner withInner) {//必須要有一個非默認構造方法,否則還是報錯-- 需要一個外部類的實例 
		withInner.super();
	}

	public static void main(String[] args) {
		WithInner withInner = new WithInner();
		InheritInner lInheritInner = new InheritInner(withInner);
	}

}

10.10 內部類可以被覆蓋麼

舉個例子 一個類創建了一個內部類,用另一個類繼承外圍類,在這個“另一個類中重新定義同名的內部類”,看起來好像是覆蓋了第一個類的內部類,真實情況是這樣嗎?

public 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{//繼承了Egg之後 各自的內部類沒有關聯,各自在自己的命名空間,相互獨立不存在覆蓋的問題
	public class Yolk{//與Egg中的Yolk類沒有關係
		public Yolk() {
			System.out.println("BigEgg.yolk");
		}
	}
	
	public static void main(String [] args){
		new BigEgg();
	}

}

輸出爲

new Egg
Egg.yolk

從例子的輸出結果看 內部類不可以被覆蓋,但是可以明確的繼承某一個內部類(上面這個例子不是繼承和覆蓋)

public class Egg2 {
	private Yolk y;
	protected class Yolk{
		public Yolk() {
			System.out.println("Egg2.Yolk()");
		}
		
		public void f() {
			System.out.println("Egg2.f()");
		}
	}
	
	public Egg2() {
		System.out.println("new Egg2()");
		y = new Yolk();
	}
	
	public void insertYolk(Yolk yy){
		y = yy;
	}
	
	public void g(){
		y.f();
	}

}
public class BigEgg2 extends Egg2 {
	public BigEgg2() {
		insertYolk(new Yolk());
	}

	public class Yolk extends Egg2.Yolk {//指定繼承Egg2中的Yolk
		public Yolk() {
			System.out.println("BigEgg2.Yolk()");
		}

		public void f() {
			System.out.println("BigEgg2.Yolk().f()");
		}
	}

	public static void main(String[] args) {
		Egg2 e2 = new BigEgg2();
		e2.g();
	}
}

/*
輸出
new Egg2()
Egg2.Yolk()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk().f()

分析
new Egg2()//創建BigEgg2對象先調用父類構造方法Egg2
Egg2.Yolk()//父類構造方法中調用了Egg2.Yolk的構造方法
Egg2.Yolk()//BigEgg2.Yolk的對象前先調用父類構造方法Egg2.Yolk()
BigEgg2.Yolk()//創建子類對象
BigEgg2.Yolk().f()//調用子類方法
*/

10.11 局部內部類 VS 匿名內部類

很多情況下 我們可以使用局部內部類(在方法中) 或者匿名內部類,那麼他們有什麼不同

public interface Counter {
	int next();
}

//局部內部類VS匿名內部類
public class LocalInnerClass {
	private int count = 0;

	//局部內部類
	Counter getCounter(final String name){
		class LocalCounter implements Counter{
			public LocalCounter() {
				System.out.println("創建局部內部類");
			}
			@Override
			public int next() {
				System.out.println(name);
				return count++;
			}
		}
		return new LocalCounter();
	}
	
	//相同的功能用匿名內部類實現
	Counter getCounter2(final String name){
		return new Counter() {
			//匿名內部類沒有有名字的構造方法
			{
				System.out.println("創建匿名內部類");
			}
			@Override
			public int next() {
				System.out.println(name);
				return count++;
			}
		};
	}
	
	public static void main(String [] args) {
		LocalInnerClass lic = new LocalInnerClass();
		Counter c1 = lic.getCounter("局部內部類");
		Counter c2 = lic.getCounter2("匿名內部類");
		for (int i = 0; i < 5; i++) {
			System.out.println(c1.next());
		}
		for (int i = 0; i < 5; i++) {
			System.out.println(c2.next());
		}
	}

}
/*
局部內部類VS匿名內部類:
局部內部類可以重載構造器,而匿名內部類只能用於實例初始化.
使用局部內部類的而不使用匿名內部類的另一個理由:需要不止一個該內部類的對象.
*/

10.12 內部類標識符 $

java在編譯的時候每個類都會產生一個.class文件該文件包含創建該對象的全部信息,而內部類也不例外,不過內部類比較特殊,它的命名方式是"外圍類名$內部類名.class"
該命名在我們分析內存泄漏時很有用,其他暫時不知道還有什麼作用.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章