推薦學習:《》
我們之前學過什麼是類,那麼抽象類是不是也是類的一種呢?
聽名字就感覺好抽象呀!說對了,他就是抽象的,不是具體的。在類中沒有包含足夠的資訊來描繪一個具體的物件,這樣的類稱為抽象類。
來看一個抽象類的例子
// 抽象類和抽象方法需要被 abstract 關鍵字修飾 abstract class Shape { // 抽象類中的方法一般要求都是抽象方法,抽象方法沒有方法體 abstract void draw(); }
大家覺得這個抽象類是不是什麼也沒幹,他唯一的方法draw()還是空的。
像這樣的類是不是就沒有包含足夠的資訊來描繪一個具體的物件,自然也就不能範例化物件了。不信你看:
那既然一個類不能範例化,那這種抽象類存在的意義是什麼?
抽象類存在的一個最大意義就是被繼承,當被繼承後就可以利用抽象類實現多型。
來看一段程式碼
// 抽象類和抽象方法需要被 abstract 關鍵字修飾 abstract class Shape { // 抽象類中的方法一般要求都是抽象方法,抽象方法沒有方法體 abstract void draw(); } // 當一個普通類繼承一個抽象類後,這個普通類必須重寫抽象類中的方法 class Cycle extends Shape { @Override void draw() { // 重寫抽象類中的draw方法 System.out.println("畫一個圓圈"); } } public class Test4 { public static void main(String[] args) { //Shape shape = new Shape(); 抽象類雖然不能直接範例化 // 但可以把一個普通類物件傳給一個抽象類的參照呀,即父類別參照指向子類物件 Shape shape = new Cycle(); // 這稱作:向上轉型 /*Cycle cycle = new Cycle(); Shape shape = cycle // 這是向上轉型的另一種寫法 */ shape.draw(); // 通過父類別參照呼叫被子類重寫的方法 } }
執行之後你就會發現神奇的一幕:
大家在看完了程式碼可能會有很多疑問,別急咱們一個一個的說,
什麼是向上轉型:一句話總結就是「父類別參照指向子類物件」
向上轉型後的變化
向上轉型的作用
這樣的話就我們上面的程式碼就可以理解了
看來,我們可以通過子類對抽象類的繼承和重寫,抽象類還真有點用呀!
但這和多型有什麼關係呢,抽象類用起來這麼麻煩,我還不如直接用普通類,也能達到這樣的效果,還不用再寫一個子類呢?
那行,你再看看下面的程式碼,你就知道抽象類在實現多型時的好處了。
abstract class Shape { public abstract void draw(); // 抽象方法不能裡有具體的語句 } // 當一個普通類繼承一個抽象類的時候,再這個子類中必須重寫抽象類中的抽象方法 class Cycle extends Shape { @Override // 如果不重寫會報錯,但如果繼承的是普通類則不會報錯,用抽象類更安全 public void draw() { System.out.println("畫一個圓圈"); } } class Flower extends Shape { // 不同的子類對父類別的draw方法進行了不同的重寫 @Override public void draw() { System.out.println("畫一朵花"); } } class Square extends Shape { @Override public void draw() { System.out.println("畫一個正方形"); } } public class Test4 { public static void main(String[] args) { Cycle cycle = new Cycle(); // 子類參照cycle Flower flower = new Flower(); // 子類參照flower Square square = new Square(); // 陣列的型別是Shape,即陣列中每一個元素都是一個父類別參照 // 在這個過程其實也發生了向上轉型,對抽象類中的方法進行了重寫 Shape[] shapes = {cycle, flower, square}; // 父類別參照參照不同的子類物件 for (int i = 0; i < shapes.length; i++) { Shape shape = shapes[i]; // 父類別參照shape指向—>當前所對應的子類物件 shape.draw(); // 通過父類別參照呼叫子類重寫的draw方法 } } }
呼叫同一個方法竟然列印出了不同的結果,這難道就是所謂的多型
是不是有點懵,下面我們來解釋一下
// 對上面的程式碼補充一下 // 可能你對 Shape[] shapes = {cycle, flower, square};不太理解 // 但上面的程式碼就相當於 Shape[] shapes1 = new Shape[3]; // 有三個不同的子類物件呀!陣列大小為3 // (將指向->子類物件)的子類參照賦值給父類別物件,不就相當於該夫類參照指向->所對應的子類物件嗎 //這是向上轉型的另一種寫法,應為前面已經範例化了子類物件 Cycle cycle = new Cycle(); shapes1[0] = cycle; // 如果前面沒範例化子類物件,就要寫成shape1[0] = new Cycle shapes1[1] = flower; shapes1[2] = square;
對於多型來說,他有這三個要素
回頭再看一下我們的程式碼,是不是就剛好符合了多型的三要素。
當我們的父類別參照指向不同的子類物件時,當我們呼叫同一個draw方法時卻輸出了不同的結果。(其實就是該方法再子類中被重寫成了不同形式)這就叫做多型 。
嘻嘻,其實只要只要結合著例子來看,多型也沒那麼難理解呀
那為啥一定要用抽象類呢?我一個普通類繼承普通類來實現多型不可以嗎
當然可以,但不太安全有風險;
但如果是抽象類的話,就不一樣了
從這我們也可以看出,當用抽象類的時候,編譯器自動就對我們是否重寫進行了校驗,而充分利用編譯器的校驗, 在實際開發中是非常有意義的 。所以說抽象類還是有用的
好了,相信到這裡你對抽象類也有了一個大概的認識,下面來簡單做一下總結
抽象類是從多個類中抽象出來的模板,如果將這種抽象進行的更徹底,則可以提煉出一種更加特殊的「抽象類」——介面(Interface)。
介面是Java中最重要的概念之一,它可以被理解為一種特殊的類,不同的是介面的成員沒有執行體,是由全域性常數和公共的抽象方法所組成。
如何定義一個介面呢?下面我們來看一個栗子
//介面的定義格式與定義類的格式基本相同,將class關鍵字換成 interface 關鍵字,就定義了一個介面 public interface 介面名稱{ // 定義變數 int a = 10; // 介面當中的成員變數預設都是public static final // 抽象方法 public abstract void method1(); // public abstract 是固定搭配,可以不寫 void method2(); // 介面當中的成員方法預設都是public abstract, 更推薦用第二種來定義方法 }
可以看到介面和類其實還是有很多相似點:
介面中也包含抽象方法,所以也不能直接範例化介面,那麼我們怎麼用介面呢?
哈哈,很簡單,我們再用一個普通類實現這個介面不就行了嗎,不同的是抽象類是被子類來繼承而實現的,而介面與類之間則是用關鍵字implements來實現。
就像普通類實現實現抽象類一樣,一個類實現某個介面則必須實現該介面中的抽象方法,否則該類必須被定義為抽象類。
鐵汁們!剛才我們是用抽象類來實現多型,那麼現在我們可以嘗試用介面來實現多型,
介面可以看成是一種特殊的類,只能用 interface 關鍵字修飾 interface IShape { int a = 10; 介面當中的成員變數預設都是public static final int b = 23; void draw(); 介面當中的成員方法一般只能是抽象方法,預設是public abstract(JDK1.8以前) default void show() { System.out.println("介面中的其他方法");//介面中的其他方法也可以實現,但要用default修飾 } public static void test() { System.out.println("這是介面當中的一個靜態的方法"); } } // 一個普通的類要想實現介面,可以用implement, //因為介面也是抽象方法的,所以實現介面的這個類也要重寫抽象方法 class Cycle implements IShape { @Override public void draw() { System.out.println("畫一個圓圈"); } } class Square implements IShape { @Override public void draw() { System.out.println("畫一個正方形"); } } class Flower implements IShape { @Override public void draw() { System.out.println("畫一朵花"); } } public class Test4 { public static void main(String[] args) { // IShape iShape = new IShape(); 介面也不能直接範例化 Cycle cycle = new Cycle(); Square square = new Square(); Flower flower = new Flower(); // 這裡的IShape介面就相當與抽象類中父類別,介面型別也是一種參照型別 IShape[] iShapes = {cycle, square, flower}; // 這個過程其實就發生了向上轉型 for (IShape iShape : iShapes) { // 增強型的for—each迴圈,也可以寫成普通的for迴圈形式 iShape.draw(); // 通過重寫實現了多型 } } } 參照變數cycle和square都賦值給了Shape型別的參照變數shape, 但當執行shape.draw()時,java虛擬機器器到底要呼叫誰重寫的的draw方法, 就看此時介面參照的是那個物件的,是shape的、還是cycle的
看一下執行結果
看完程式碼你可能有點暈,但沒關係。一般介面咱也不這麼用,直接使用抽象類不就好了(我只是演示一下用介面也能實現多型)
下面我們來總結一下Java中介面的幾個主要特點
那麼介面一般用在什麼地方呢?
下面就讓我們來看看介面的正確用法:幫助java實現「 多繼承 」
由於 Java 中單繼承的特性,導致一個類只能繼承一個類,但是可以實現一個或多個介面,此時可以使用介面。 class Animal { String name; // 不能使用private,後面的子類也要用 public Animal(String name) { // 父類別的自定義的構造方法 this.name = name; } } interface IFlying { // 自定義多種介面 void fly(); } interface IRunning { void run(); } interface ISwimming { void swimming(); } // 小鴨子,不僅會跑,還會游泳、飛行 一個類繼承父類別,並實現多個介面,間接的解決java中不能多繼承的問題 class Duck extends Animal implements IRunning, ISwimming, IFlying { public Duck(String name) { // 子類構造方法 super(name); // 必須在子類構造方法的第一行 // 在給實現子類的構造方法前,先要用super()呼叫實現父類別的構造方法,比較先有父後有子呀! // 因為父類別自己定義了構造方法,編譯器不會自動給給子類構造方法中新增super();來實現父類別的構造方法,需要我們自己實現 } // 對介面中的抽象方法進行重寫 @Override public void fly() { System.out.println(this.name + "正在用翅膀飛"); } @Override public void run() { System.out.println(this.name + "正在用兩條腿跑"); } @Override public void swimming() { System.out.println(this.name + "正在漂在水上"); } } public class 介面的使用 { // 不用學我用中文名作為類名,我只是為演示方便 public static void main(String[] args) { Duck duck = new Duck("第一個小鴨子"); // 範例化鴨子物件 duck.fly(); // 通過參照 變數名.方法名 輸出重寫後的方法 duck.run(); duck.swimming(); } } 有人可能會說幹嘛用介面,我直接在父類別Animal中實現fly、run、swimming這些屬性, 然後不同的動物子類再繼承父類別這些方法不行嗎? 但問題是,鴨子會fly、swimming,那貓會飛和游泳嗎?你再寫個其他動物的子類是不是就不行了 而用介面呢?我們只是把這種飛、游泳的行為給抽象出來了, 只要一個子類有這種行為,他就可以實現相對應的介面,介面是更加靈活的
上面的程式碼展示了 Java 物件導向程式設計中最常見的用法: 一個類繼承一個父類別, 同時實現多個介面。
繼承表達的含義是 is - a 語意, 而介面表達的含義是 具有 xxx 特性 ,能實現介面的類和該介面並不一定有is_a的關係,只要該類有這個介面的特性就行
貓是一種動物, 具有會跑的特性.
青蛙也是一種動物, 既能跑, 也能游泳
鴨子也是一種動物, 既能跑, 也能遊, 還能飛
這樣設計有什麼好處呢? 時刻牢記多型的好處, 讓程式猿忘記型別. 有了介面之後, 類的使用者就不必關注具體型別,只要這個類有有這個特性就好。
舉個栗子
class Robot implements IRunning { private String name; public Robot(String name) { this.name = name; } // 對run方法進行重寫 @Override public void run() { System.out.println("機器人" + this.name + "正在跑"); } } public class Test4 { public static void main(String[] args) { Robot robot1 = new Robot("圖圖"); robot1.run(); } } // 執行結果 機器人圖圖正在跑
只要能跑就行,管他是機器人還是動物呢
推薦學習:《》
以上就是完全掌握Java中的抽象類和介面的詳細內容,更多請關注TW511.COM其它相關文章!