1. 程式人生 > >重新開始學Java——抽象類、介面、內部類

重新開始學Java——抽象類、介面、內部類

抽象類

為什麼要定義抽象方法:如果定義某個方法時不能確定該方法的具體實現細節; 比如定義 Person 類的 eat 方法時, 不能確定其具體實現細節,因為中國人、 西方國家的人、 南亞國家的人吃飯方式不一樣。 可以把該方法定義成一個抽象方法,具體 的實現細節,交給其後代(子類)來實現。

抽象方法的定義

使用 abstract 關鍵字修飾方法的定義,方法體必須為空(否則就不是抽象方法),抽象方法必須是非靜態的(抽象方法不能被 static 修飾), 抽象方法不能被 final 修飾、 不能被 private 修飾.

[ 修飾符 ] abstract 返回值型別 methodName() ; //注意這裡沒有 { }

抽象類的定義

為什麼要定義抽象類:定義抽象方法的類, 必須被定義成抽象類

抽象類的定義方法

[ 修飾符 ] abstract class className {
	//使用 abstract 修飾類定義
}

抽象類的特徵

  • 抽象類不能被例項化 (有構造但僅供子類呼叫)
  • 抽象類中可包含屬性、 方法、 構造、 內部類、 列舉、 程式碼塊等
  • 其中的方法可以是抽象方法, 也可以是已實現方法
  • 含有抽象方法的類, 必須被定義成抽象類
  • 這個抽象方法可能是自定義、 繼承來的、 或實現自介面的,否則就要全部實現其中的抽象方法
  • 抽象類中, 可以沒有抽象方法

DEMO

/**
* 為什麼要有抽象類
* 1、含有抽象方法的類,必須被定義成抽象類;
* 但是,抽象類未必非要有抽象方法
* 2、如果期望當前類不能被例項化,
* 而是交給子類完成例項化操作,可以定義抽象類
* (抽象類有構造方法,抽象類不能被例項化(直接建立該類的物件))
* Person p = new Person(); // 錯誤的
*/
public abstract class Person {
	// 只要有花括號,就可以執行,這樣的方法已經實現過了
	/**當一個方法在定義時無法確定其實現細節(具體處理什麼、怎麼處理)
	* 如果一個方法不是native 修飾的,
	* 當它沒有方法體時,這個方法是不能被執行的,此時這個方法就是一個抽象的方法,
	* 需要使用抽象關鍵字來修飾(abstract)
	*/
	public abstract void eat(String food);
	/**
	* abstract 修飾方法時,不能與 static 、 final連用
	*/
}
/**
* 子類繼承某個抽象類
* 1、如果子類依然不確定某個方法的實現細節(不實現繼承自父類的抽象方法),
* 則可以將該類繼續宣告為抽象類
* 2、如果子類不希望是抽象類,必須實現繼承自父類的抽象方法
*/
public class Sinaean extends Person{
	public Sinaean(){
		super();
	}
	// 這個方法不再是抽象方法了,而且可以有方法體
	@Override
	public void eat(String food) {
		System.out.println("中國人大部分都用筷子吃"+ food);
	}
}
public class Thai extends Person{
	@Override
	public void eat(String food) {
		System.out.println("泰國人有時候用手抓著吃: "+ food);
	}
}

/**
* 建立抽象類的例項:
* 1、建立其子類型別的例項(這是本質)
* 2、可以通過靜態方法來獲得其例項(本質還是建立子類型別物件 )
*/
public class Main {
	public static void main(String[] args) {
		// 不能例項化Person 型別:抽象類不能被例項化
		// Person p = new Person();//Cannot instantiate the type Person
		// 宣告一個Person 型別的變數p(p的編譯時型別是 Person)
		// 建立抽象類的子類型別的物件,並將其堆記憶體中首地址賦值給棧空間中的p變數
		Person p =new Sinaean();
		p.eat("火鍋");
		System.out.println("執行時型別: " + p.getClass());System.out.println("記憶體中的首地址是: "+ System.identityHashCode(p));
		// 建立抽象類的子類型別的物件,並將其堆記憶體中首地址賦值給棧空間中的p變數
		// p 變數中原來儲存的地址將被覆蓋
		p = new Thai();
		p.eat("米飯");
		System.out.println("執行時型別: " + p.getClass());
		System.out.println("記憶體中的首地址是: "+ System.identityHashCode(p));
		Calendar c = Calendar.getInstance(); // 通過靜態方法來獲得一個例項
		Class<?> clazz = c.getClass();// 獲得c 所引用的物件的真實型別(執行時型別)
		System.out.println(clazz);
		Class<?> superClass = clazz.getSuperclass();// 獲得clazz的父類
		System.out.println(superClass);
	}
}

總結

抽象類的特點
1、抽象類【有構造】,但是不能被例項化
	抽象類的構造方法專供子類呼叫(構造方法也是可以執行的)
2、抽象類中可以有抽象方法,也可以沒有
	含有抽象方法的類必須是抽象類(參看Person中的第一點)
	抽象類中可以沒有抽象方法(參看Person中的第二點)
3、怎麼建立抽象類的例項:建立其子類型別的例項(這是本質)
	待建立物件的子類型別必須是非抽象類
	不一定非要是直接子類,間接子類也可以
	可以通過靜態方法來獲得其例項(Calendar.getInstance() )
	Calendar c = Calendar.getInstance();
4、應該選擇哪種方式來建立抽象類的例項:
a>、如果當前抽象類中有靜態方法,則優先使用靜態方法
b>、如果子類中有靜態方法返回相應例項,用子類的靜態方法
c>、尋找非抽象的子類,建立子類型別的物件即可
d>、自己繼承這個類並實現其中的抽象方法,然後建立例項
注意:有時為了實現我們的需求,可能會不呼叫靜態方法來獲得例項,而是選擇建立子類物件

介面

介面是一種比抽象類更抽象的型別;介面是從多個相似的類中抽象出來的規範: 它定 義了某一批類(介面的實現類或實現類的子類)所要必須遵循的規範。 介面只定義常量 或方法, 而不關注方法的實現細節,介面體現了規範和實現相分離的設計哲學。

定義介面

[ 修飾符 ] interface InterfaceName {
	定義在介面中的常量 ( 0 到 n 個)
	定義在介面中的抽象方法 ( 0 到 n 個)
	static 修飾的方法 ( 0 到 n 個)
	default 修飾的方法( 0 到 n 個)
}

介面中的成員

  • 可以包含屬性(只能是常量)
  • 系統會對沒有顯式使用 public static final 修飾的變數追加這些修飾
  • 可以包含方法(必須是抽象的例項方法)
  • 介面中不允許有非抽象的方法
  • 介面中可以存在靜態方法( 用 static 修飾的靜態方法)
  • 介面中可以存在預設方法( 用 default 修飾的方法 )
  • 可以包含內部類或內部介面
  • 可以包含列舉類
  • 不能包含構造方法
  • 不能包含程式碼塊

介面的繼承和實現

介面的繼承使用 extends 關鍵字實現:

public interface Usb1 extends Usb {};

Java 語言中的介面可以繼承多個介面: 多個介面之間使用 , 隔開 ( 英文狀態的逗 號 );子介面可以繼承父介面中的: 抽象方法、常量屬性、內部類、列舉類

類可以實現介面:使用 implements 關鍵字來實現

介面的特徵

介面中的屬性預設都是 public 、 static 、 final 型別:這些成員必須被顯式初始化;介面中的方法預設都是 public 、 abstract 型別的。

介面中根本就沒有構造方法, 也就可能通過構造來例項化,但允許定義介面型別的引用變數,該引用變數引用實現了這個介面的類的例項。

介面不能實現另一個介面, 但可以繼承多個介面。

介面必須通過實現類來實現它的抽象方法,當某個類實現了某個介面時, 必須實現其中所有的抽象方法,或者是不實現其中的抽象方法, 而把該類定義成抽象類。

類只能繼承一個類, 但可以實現多個介面,多個介面之間用逗號分開。

介面和抽象類的異同

共同點:
	介面和抽象類都不能被例項化
	介面和抽象類都處於繼承樹的頂端
	介面和抽象類都可以包含抽象方法
	實現介面或繼承抽象類的普通類必須實現其中的抽象方法
區別
	抽象類中可以有非抽象方法, 介面中只能有抽象方法或static修飾的方法或default修飾的方法
	一個類只能繼承一個直接父類, 而介面可以實現多繼承
	抽象類可定義靜態屬性和普通屬性, 而介面只能定義靜態屬性
	抽象類有自己的構造, 介面完全沒有
	抽象類中可以有程式碼塊, 介面中不可以有

DEMO

/**
* 宣告介面,並確定介面中可以有什麼
* 1、常量
* 2、抽象方法
* 3、 default 修飾的非抽象方法(JDK1.8開始)* 4、介面沒有構造方法
*/
public interface Usb {
	// 介面沒有構造方法
	// public Usb(){}
	/**
	* 介面中只能定義常量(沒有不是不是常量的屬性)
	* 1、介面中所有的屬性預設都是 public static final 修飾的
	* 2、常量的命名:所有字母都是大寫,如果有多個單詞,中間用下劃線隔開
	* */
	int POWER_UNIT = 100 ;// 充當供電單位
	/**
	* JDK1.8 之前 僅允許在介面中宣告抽象方法
	* 所有的方法都是 public abstrct 修飾的
	*/
	void power();
	/**
	* JDK1.8 開始,允許定義被default修飾的非抽象方法
	* 這個方法是個public 修飾的非靜態方法(子類或子介面可以重寫)
	*/
	default void show(){
		System.out.println("每次供電單位是: "+POWER_UNIT) ;
	}
}

/**
* 1、類 可以實現介面,用關鍵字implements來完成實現
* 2、如果本來不希望是抽象類,則需要實現從介面"繼承"的所有抽象方法
*/
public class MiUsb extends Object implements Usb {
	/**
	* MiUsb中都有什麼
	* 從Object中繼承的所有方法
	* 從Usb中繼承的常量
	* 從Usb中繼承的default的方法(JDK1.8開始)
	* 實現了所有的抽象方法
	*/
	@Override
	public void power() {
		System.out.println("小米Usb充電器,供電單位: "+POWER_UNIT);
	}
	@Override
	public void show() {
		Usb.super.show();
	}
}

public class Test {
	public static void main(String[] args) {
		// 宣告一個介面型別的引用變數Usb u = null;
		// 建立實現類的例項 並將其堆記憶體首地址賦值給u變數
		u = new MiUsb();
		u.power();
	}
}

一個類實現多個介面 

public interface Transfer {
	void transmission();
}

/**
* 1、用介面繼承介面
* 2、介面可以繼承父介面中的常量、抽象方法、 default方法
* 3、一個介面可以繼承多個介面,中間用逗號隔開就行
*/
public interface UsbTypeC extends Usb , Transfer{}

/**
* 一個類可以實現多個介面,中間用逗號分隔開就可以
*/
public class OppoUsb implements UsbTypeC,Usb{
	@Override
	public void transmission() {
		System.out.println("Oppo手機");
	}
	@Override
	public void power() {
		System.out.println("Oppo 手機,供電單位"+ POWER_UNIT);
	}
}

public class Test2 {
	public static void main(String[] args) {
		// 宣告一個介面型別的引用變數
		OppoUsb u = null;
		u = new OppoUsb();
		u.power();// 實現了Usb介面中的方法
		u.transmission();// 實現了Transfer介面中的方法
	}
}

內部類

內部類的分類如下:

成員內部類:
	例項內部類
	靜態內部類
區域性內部類:
	匿名內部類

DEMO

public class Human {/* 類體括號 */
	public static void main(String[] args){ // main 方法的方法體開始
		int a = 250;
		System.out.println(a);
		class ON{ // 區域性內部類(Local Inner Class)
		}
		ON oo = new ON();
		System.out.println(oo);
		class OFF{ // 區域性內部類(Local Inner Class)
		}
	}// main 方法的方法體結束static String earth; // 屬性:靜態屬性( 類屬性 )
	
	String name ; // 屬性 :例項屬性(例項變數)
	static class Country{ // 靜態內部類[ static Inner Class]
	}
	class Head{// 例項內部類 (成員內部類) [ Member Inner Class ]
	}
	class Hand{// 例項內部類
	}
}

獲得到自己的內部類

/**
* 獲得某個類內部的所有的靜態內部類和所有的成員內部類
* 注意:不能獲得到區域性內部類
*/
public class GetInnerClass {
	public static void main(String[] args) {
		Class<?> c = Human.class;
		// 獲得 c 內部的內部類(靜態內部類、成員內部類)
		Class<?>[] classes = c.getDeclaredClasses();// 獲得本類內宣告的非 區域性內部類
		for (int i = 0; i < classes.length; i++) {
			Class<?> cc = classes[i];
			System.out.println(cc);
		}
	}
}

也可以通過一個內部類獲取自己宣告在哪個類內部

public class GetOutterClass {
	public static void main(String[] args) {
		Class<?> c = Human.Country.class;
		// 獲得某個內部類宣告在那個外部類中
		Class<?> oc = c.getDeclaringClass(); // 獲得宣告自己的 那個類
		System.out.println(oc);
	}
}

建立靜態內部類的例項

public class GetInstance1 {
	public static void main(String[] args) {
		/** 靜態內部類的例項 */
		Human.Country c = new Human.Country();
		System.out.println(c);
		/** 例項內部類的例項 */
		Human h = new Human();// 建立外部類的例項
		Human.Hand hand = h.new Hand();// 以外部類的例項 h 為基礎,建立內部類的例項
		System.out.println(hand);
		// 或者:
		Human.Head head= new Human().new Head();
		System.out.println(head);
	}
}

匿名內部類

有一個區域性內部類,它連名字都沒有,則它就是匿名內部類,但是它有對應的.class檔案。

新建一個新的 Class,叫做 TestAnonyous1。隨後建立一個介面,叫做 USB,並提供一個方法(void transfer) 。具體在 TestAnonyous1 中的例子:用匿 名內部類實現介面。

DEMO

/**
* 建立匿名內部類
*/
public class TestAnonymours1 {
	public static void main(String[] args) {
		// 編譯時型別:變數u 宣告的型別是USB
		// 用匿名內部類來實現介面
		USB u = new USB(){
		@Override
		public void transfer() {
			System.out.println("USB正在傳輸");
		}
		};// 把USB當成屍體,結果鬼{}上身了,就能例項化了
		u.transfer();
		System.out.println(System.identityHashCode(u));
		// 獲得建立的例項的執行時型別
		Class<?> c = u.getClass();// 任何一個物件都可以通過getClass來獲得其 執行時型別
		System.out.println(c);
		Class<?> oc = c.getDeclaringClass();// 嘗試獲得宣告自己的那個外部類
		System.out.println(oc);// null 說明 匿名內部類不是直接宣告在類體內部的
		Class<?>[] inters = c.getInterfaces();
		for (int i = 0; i < inters.length; i++) {
			System.out.println(inters[i]);
		}
	}
}
public abstract class AbstractUSB implements USB{
// 從實現的介面中繼承了抽象方法 transfer
}
/**
* 建立匿名內部類
*/
public class TestAnonymours2 {
	public static void main(String[] args) {
	// 建立一個抽象類的例項(本質一定是建立其子類型別的例項)
	// 用匿名內部類來繼承抽象類,並實現其中的抽象方法
	AbstractUSB au = new AbstractUSB() {
		@Overridepublic void transfer() {
			System.out.println("AbstractUSB正在傳輸");
		}
	};
	au.transfer();
	Class<?> c = au.getClass();// 獲得au 物件的執行時型別
	System.out.println("匿名內部類: "+ c.getName());
	Class<?> sc = c.getSuperclass();
	System.out.println("匿名內部類的父類: " + sc.getName() );
	}
}
/**
* 建立匿名內部類
*/
public class TestAnonymours3 {
	public static void main(String[] args) {
	// 用匿名內部類繼承一個普通的類
	// 並重寫其中的方法
	Object o = new Object(){
		@Override
		public String toString(){
		return "我是鬼。。 ";
	}};
	System.out.println(o);
	System.out.println(o.getClass());
	System.out.println(o.getClass().getSuperclass());
	}
}

總結

1、內部類
	巢狀在另一個類內部的類
2、內部類的分類
	直接寫在類體括號內的:
	靜態內部類、非靜態內部類(例項內部類、成員內部類)
	不是直接寫在類體括號內,比如寫在方法中、寫在程式碼塊中:區域性內部類
	如果某個區域性內部類連名字都沒有,那它就是匿名內部類
3、問題:
	對於 Human.java 來說有一個與它對應的 Human.class 檔案,內部類是否有對應的 .class 檔案?
	有.class 檔案,對於靜態內部類、例項內部類來說,他們對應的 .class 的名稱是:
		外部類類名$內部類類名.class比如 Human 類中的 Country 類對應的 位元組碼檔案的名稱是:Human$Country.class
	對於區域性內部類(有名稱的)來說:他們對應的 .class 檔名稱是:外部類類名$Number 內部類類名.class
		其中的 Number 是 使用 該名稱 的 內部類 在 外部類 出現的位置(第幾個)
		
4、一個類能否獲取到自己的內部類(靜態內部類、成員內部類)
	GetInnerClass.java
	
5、一個內部類能否獲取自己宣告在哪個類內部: GetOutterClass.java

6、建立內部類的例項
	a>、區域性內部類的例項,只能在當前的程式碼塊內部使用,
	比如 Human 類內部的 main 方法的 ON 類,則這個類只能在 main 方法內部使用
		class ON{ // 區域性內部類(Local Inner Class)
		}
		ON oo = new ON();// 建立區域性內部類的例項
		System.out.println(oo);
	b>、 建立靜態內部類的例項:
		// 外部類.靜態內部類 變數名 = new 外部類.靜態內部類()Human.Country c = new Human.Country();
	c>、建立例項內部類的例項:
		Human h = new Human();// 建立外部類的例項
		外部類.例項內部類 變數名 = 外部類例項.new 例項內部類();
		Human.Hand hand = h .new Hand();
7、匿名內部類
	有一個區域性內部類,它連名字都沒有,則它就是匿名內部類,但是它有對應的.class 檔案
	匿名內部類對應的.class 檔名 是外部類類名$數字.class
	a>、用匿名內部類實現介面: TestAnonymous1.java
	b>、用匿名內部類繼承抽象類: TestAnonymous2.java
	c>、用匿名內部類繼承普通的類: TestAnon