重新開始學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