您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java中的抽象類和接口怎么理解”,在日常操作中,相信很多人在Java中的抽象類和接口怎么理解問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java中的抽象類和接口怎么理解”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
我們之前學(xué)過什么是類,那么抽象類是不是也是類的一種呢?
聽名字就感覺好抽象呀!說對了,他就是抽象的,不是具體的。在類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類稱為抽象類。
來看一個抽象類的例子
// 抽象類和抽象方法需要被 abstract 關(guān)鍵字修飾 abstract class Shape { // 抽象類中的方法一般要求都是抽象方法,抽象方法沒有方法體 abstract void draw(); }
大家覺得這個抽象類是不是什么也沒干,他唯一的方法draw()還是空的。
像這樣的類是不是就沒有包含足夠的信息來描繪一個具體的對象,自然也就不能實例化對象了。不信你看:
那既然一個類不能實例化,那這種抽象類存在的意義是什么呀別急,存在即合理,聽我慢慢道來。
抽象類存在的一個最大意義就是被繼承,當(dāng)被繼承后就可以利用抽象類實現(xiàn)多態(tài)。
來看一段代碼
// 抽象類和抽象方法需要被 abstract 關(guān)鍵字修飾 abstract class Shape { // 抽象類中的方法一般要求都是抽象方法,抽象方法沒有方法體 abstract void draw(); } // 當(dāng)一個普通類繼承一個抽象類后,這個普通類必須重寫抽象類中的方法 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(); // 這稱作:向上轉(zhuǎn)型 /*Cycle cycle = new Cycle(); Shape shape = cycle // 這是向上轉(zhuǎn)型的另一種寫法 */ shape.draw(); // 通過父類引用調(diào)用被子類重寫的方法 } }
運行之后你就會發(fā)現(xiàn)神奇的一幕:
大家在看完了代碼可能會有很多疑問,別急咱們一個一個的說,
什么是向上轉(zhuǎn)型:一句話總結(jié)就是“父類引用指向子類對象”
向上轉(zhuǎn)型后的變化
關(guān)于方法:父類引用可以調(diào)用子類和父類公用的方法(如果子類重寫了父類的方法,則調(diào)用子類的方法),但子類特有的方法無法調(diào)用。
關(guān)于屬性: 父類引用可以調(diào)用父類的屬性,不可以調(diào)用子類的屬性
向上轉(zhuǎn)型的作用
減少一些重復(fù)性的代碼
對象實例化的時候可以根據(jù)不同需求實例化不同的對象
這樣的話就我們上面的代碼就可以理解了
看來,我們可以通過子類對抽象類的繼承和重寫,抽象類還真有點用呀!
但這和多態(tài)有什么關(guān)系呢,抽象類用起來這么麻煩,我還不如直接用普通類,也能達(dá)到這樣的效果,還不用再寫一個子類呢
那行,你再看看下面的代碼,你就知道抽象類在實現(xiàn)多態(tài)時的好處了。
abstract class Shape { public abstract void draw(); // 抽象方法不能里有具體的語句 } // 當(dāng)一個普通類繼承一個抽象類的時候,再這個子類中必須重寫抽象類中的抽象方法 class Cycle extends Shape { @Override // 如果不重寫會報錯,但如果繼承的是普通類則不會報錯,用抽象類更安全 public void draw() { System.out.println("畫一個圓圈"); } } class Flower extends Shape { // 不同的子類對父類的draw方法進(jìn)行了不同的重寫 @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(); // 數(shù)組的類型是Shape,即數(shù)組中每一個元素都是一個父類引用 // 在這個過程其實也發(fā)生了向上轉(zhuǎn)型,對抽象類中的方法進(jìn)行了重寫 Shape[] shapes = {cycle, flower, square}; // 父類引用引用不同的子類對象 for (int i = 0; i < shapes.length; i++) { Shape shape = shapes[i]; // 父類引用shape指向—>當(dāng)前所對應(yīng)的子類對象 shape.draw(); // 通過父類引用調(diào)用子類重寫的draw方法 } } }
調(diào)用同一個方法竟然打印出了不同的結(jié)果,這難道就是所謂的多態(tài)
是不是有點懵,下面我們來解釋一下
// 對上面的代碼補(bǔ)充一下 // 可能你對 Shape[] shapes = {cycle, flower, square};不太理解 // 但上面的代碼就相當(dāng)于 Shape[] shapes1 = new Shape[3]; // 有三個不同的子類對象呀!數(shù)組大小為3 // (將指向->子類對象)的子類引用賦值給父類對象,不就相當(dāng)于該夫類引用指向->所對應(yīng)的子類對象嗎 //這是向上轉(zhuǎn)型的另一種寫法,應(yīng)為前面已經(jīng)實例化了子類對象 Cycle cycle = new Cycle(); shapes1[0] = cycle; // 如果前面沒實例化子類對象,就要寫成shape1[0] = new Cycle shapes1[1] = flower; shapes1[2] = square;
對于多態(tài)來說,他有這三個要素
繼承(我們剛才的Cycle類繼承Shape抽象類)
重寫(我們子類對draw方法的重寫)
父類指向子類對象(就是shape1[0] = cycle -->也可以稱作向上轉(zhuǎn)型)
回頭再看一下我們的代碼,是不是就剛好符合了多態(tài)的三要素。
當(dāng)我們的父類引用指向不同的子類對象時,當(dāng)我們調(diào)用同一個draw方法時卻輸出了不同的結(jié)果。(其實就是該方法再子類中被重寫成了不同形式)這就叫做多態(tài) 。
那為啥一定要用抽象類呢?我一個普通類繼承普通類來實現(xiàn)多態(tài)不可以嗎?
當(dāng)然可以,但不太安全有風(fēng)險;
但如果是抽象類的話,就不一樣了
好了,相信到這里你對抽象類也有了一個大概的認(rèn)識,下面我們來簡單做一下總結(jié)
使用abstract修飾的類或方法,就抽象類或者抽象方法
抽象類是不能具體的描述一個對象,不能用抽象類直接實例化對象
抽象類里面的成員變量和成員方法,都是和普通類一樣的,只不過就是不能進(jìn)行實例化了
當(dāng)一個普通類繼承這個抽象類后,那么這個普通類必須重寫抽象類當(dāng)中的所有的抽象方法(我們之前說過抽象類是不具體的,沒有包含足夠的信息來描述一個對象,所以我們需要把他補(bǔ)充完整)
但當(dāng)一個抽象類A繼承了抽象類B,這是抽象類A就可以不重寫抽象類B當(dāng)中的抽象方法
final不能修飾抽象類和抽象方法(因為抽象類存在的最大意義就是被繼承,而被final修飾的不能被繼承,final和抽象,他們兩個是天敵)
抽象方法不能被private修飾(抽象方法一般都是要被重寫的,你被private修飾了,還怎么重寫)
抽象類當(dāng)中不一定有抽象方法,但如果一個類中有抽象方法,那么這個類一定是抽象類。
抽象類是從多個類中抽象出來的模板,如果將這種抽象進(jìn)行的更徹底,則可以提煉出一種更加特殊的“抽象類”接口(Interface)。
接口是Java中最重要的概念之一,它可以被理解為一種特殊的類,不同的是接口的成員沒有執(zhí)行體,是由全局常量和公共的抽象方法所組成。
如何定義一個接口呢?下面我們來看一個栗子
//接口的定義格式與定義類的格式基本相同,將class關(guān)鍵字換成 interface 關(guān)鍵字,就定義了一個接口 public interface 接口名稱{ // 定義變量 int a = 10; // 接口當(dāng)中的成員變量默認(rèn)都是public static final // 抽象方法 public abstract void method1(); // public abstract 是固定搭配,可以不寫 void method2(); // 接口當(dāng)中的成員方法默認(rèn)都是public abstract, 更推薦用第二種來定義方法 }
可以看到接口和類其實還是有很多相似點:
接口中也包含抽象方法,所以也不能直接實例化接口,那么我們怎么用接口呢?
很簡單,我們再用一個普通類實現(xiàn)這個接口不就行了嗎?,不同的是抽象類是被子類來繼承而實現(xiàn)的,而接口與類之間則是用關(guān)鍵字implements來實現(xiàn)。
就像普通類實現(xiàn)實現(xiàn)抽象類一樣,一個類實現(xiàn)某個接口則必須實現(xiàn)該接口中的抽象方法,否則該類必須被定義為抽象類。
剛才我們是用抽象類來實現(xiàn)多態(tài),那么現(xiàn)在我們可以嘗試用接口來實現(xiàn)多態(tài)
接口可以看成是一種特殊的類,只能用 interface 關(guān)鍵字修飾 interface IShape { int a = 10; 接口當(dāng)中的成員變量默認(rèn)都是public static final int b = 23; void draw(); 接口當(dāng)中的成員方法一般只能是抽象方法,默認(rèn)是public abstract(JDK1.8以前) default void show() { System.out.println("接口中的其他方法");//接口中的其他方法也可以實現(xiàn),但要用default修飾 } public static void test() { System.out.println("這是接口當(dāng)中的一個靜態(tài)的方法"); } } // 一個普通的類要想實現(xiàn)接口,可以用implement, //因為接口也是抽象方法的,所以實現(xiàn)接口的這個類也要重寫抽象方法 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接口就相當(dāng)與抽象類中父類,接口類型也是一種引用類型 IShape[] iShapes = {cycle, square, flower}; // 這個過程其實就發(fā)生了向上轉(zhuǎn)型 for (IShape iShape : iShapes) { // 增強(qiáng)型的for—each循環(huán),也可以寫成普通的for循環(huán)形式 iShape.draw(); // 通過重寫實現(xiàn)了多態(tài) } } } 引用變量cycle和square都賦值給了Shape類型的引用變量shape, 但當(dāng)執(zhí)行shape.draw()時,java虛擬機(jī)到底要調(diào)用誰重寫的的draw方法, 就看此時接口引用的是那個對象的,是shape的、還是cycle的
看一下運行結(jié)果
下面我們來總結(jié)一下Java中接口的幾個主要特點
接口中可以包含變量和方法,變量被隱式指定為 public static final,方法被隱式指定為 public abstract(JDK 1.8 d一個類可以同時實現(xiàn)多個接口,一個類實現(xiàn)某個接口則必須實現(xiàn)該接口中的抽象方法,否則該類必須被定義為抽象類
接口支持多繼承,即一個接口可以繼承(extends)多個接口,間接解決了 Java 中類不能多繼承的問題。
那么接口一般用在什么地方呢?
一般情況下,實現(xiàn)類和它的抽象類之前具有 "is-a" 的關(guān)系,但是如果我們想達(dá)到同樣的目的,但是又不存在這種關(guān)系時,使用接口。
由于 Java 中單繼承的特性,導(dǎo)致一個類只能繼承一個類,但是可以實現(xiàn)一個或多個接口,此時可以使用接口。
下面就讓我們來看看接口的正確用法:幫助java實現(xiàn)“ 多繼承 ”????
由于 Java 中單繼承的特性,導(dǎo)致一個類只能繼承一個類,但是可以實現(xiàn)一個或多個接口,此時可以使用接口。 class Animal { String name; // 不能使用private,后面的子類也要用 public Animal(String name) { // 父類的自定義的構(gòu)造方法 this.name = name; } } interface IFlying { // 自定義多種接口 void fly(); } interface IRunning { void run(); } interface ISwimming { void swimming(); } // 小鴨子,不僅會跑,還會游泳、飛行 一個類繼承父類,并實現(xiàn)多個接口,間接的解決java中不能多繼承的問題 class Duck extends Animal implements IRunning, ISwimming, IFlying { public Duck(String name) { // 子類構(gòu)造方法 super(name); // 必須在子類構(gòu)造方法的第一行 // 在給實現(xiàn)子類的構(gòu)造方法前,先要用super()調(diào)用實現(xiàn)父類的構(gòu)造方法,比較先有父后有子呀! // 因為父類自己定義了構(gòu)造方法,編譯器不會自動給給子類構(gòu)造方法中添加super();來實現(xiàn)父類的構(gòu)造方法,需要我們自己實現(xiàn) } // 對接口中的抽象方法進(jìn)行重寫 @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 接口的使用 { // 不用學(xué)我用中文名作為類名,我只是為演示方便 public static void main(String[] args) { Duck duck = new Duck("第一個小鴨子"); // 實例化鴨子對象 duck.fly(); // 通過引用 變量名.方法名 輸出重寫后的方法 duck.run(); duck.swimming(); } } 有人可能會說干嘛用接口,我直接在父類Animal中實現(xiàn)fly、run、swimming這些屬性, 然后不同的動物子類再繼承父類這些方法不行嗎? 但問題是,鴨子會fly、swimming,那貓會飛和游泳嗎?你再寫個其他動物的子類是不是就不行了 而用接口呢?我們只是把這種飛、游泳的行為給抽象出來了, 只要一個子類有這種行為,他就可以實現(xiàn)相對應(yīng)的接口,接口是更加靈活的
上面的代碼展示了 Java 面向?qū)ο缶幊讨凶畛R姷挠梅? 一個類繼承一個父類, 同時實現(xiàn)多個接口。
繼承表達(dá)的含義是 is - a 語義, 而接口表達(dá)的含義是 具有 xxx 特性 ,能實現(xiàn)接口的類和該接口并不一定有is_a的關(guān)系,只要該類有這個接口的特性就行
貓是一種動物, 具有會跑的特性.
青蛙也是一種動物, 既能跑, 也能游泳
鴨子也是一種動物, 既能跑, 也能游, 還能飛
這樣設(shè)計有什么好處呢? 時刻牢記多態(tài)的好處, 讓程序猿忘記類型. 有了接口之后, 類的使用者就不必關(guān)注具體類型,只要這個類有有這個特性就好。
舉個栗子
class Robot implements IRunning { private String name; public Robot(String name) { this.name = name; } // 對run方法進(jìn)行重寫 @Override public void run() { System.out.println("機(jī)器人" + this.name + "正在跑"); } } public class Test4 { public static void main(String[] args) { Robot robot1 = new Robot("圖圖"); robot1.run(); } } // 執(zhí)行結(jié)果 機(jī)器人圖圖正在跑
到此,關(guān)于“Java中的抽象類和接口怎么理解”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。