詳解Java面向?qū)ο缶幊讨鄳B(tài)
Java面向?qū)ο缶幊讨鄳B(tài)
一.對(duì)于多態(tài)的理解:
通俗點(diǎn)理解,多態(tài)其實(shí)就是一詞多義,就是一種方法的多種狀態(tài),即不同的類對(duì)象,調(diào)用同一個(gè)方法名,有不同的實(shí)現(xiàn)效果,如下面這段代碼塊:
public class Test { public static void main(String[] args) { Dog dog = new Dog("豆豆"); Cat cat = new Cat("花花"); dog.eat(); cat.eat(); } }
對(duì)象dog和cat看似都調(diào)用了eat方法,都沒(méi)有傳參,按理說(shuō)輸出的結(jié)果應(yīng)該一樣,但其實(shí)不是這樣的,讓我們來(lái)看一下輸出的結(jié)果:
這就是多態(tài)的一種表現(xiàn),所屬不同類的不同對(duì)象調(diào)用同一個(gè)方法名,卻有著不同的實(shí)現(xiàn)效果。
二.多態(tài)的實(shí)現(xiàn)方法
Java中通過(guò) 方法重寫(也叫方法覆寫)、方法重載和接口實(shí)現(xiàn)多態(tài)(主要依賴于繼承機(jī)制+方法覆寫)
1.方法重載
方法重載十分好理解,就是子類和父類的方法名相同,但是參數(shù)個(gè)數(shù)或類型不一樣,返回值不作要求,這里不再贅述
2.方法重寫
對(duì)于方法重寫,通常結(jié)合向上轉(zhuǎn)型和向下轉(zhuǎn)型兩種形式進(jìn)行應(yīng)用,其中向上轉(zhuǎn)型更為常見(jiàn),向下轉(zhuǎn)型相對(duì)使用較少
(1)向上轉(zhuǎn)型:就是子類向父類轉(zhuǎn),向上轉(zhuǎn)型最大的好處就是可以實(shí)現(xiàn)參數(shù)統(tǒng)一化,向上轉(zhuǎn)型可以表現(xiàn)在三個(gè)地方:
其一:產(chǎn)生對(duì)象時(shí):
注意:用這種形式創(chuàng)建的實(shí)例化對(duì)象dog1,其能調(diào)用的方法范圍由父類Animal決定,即只能調(diào)用Animal類中的方法,而不能調(diào)用子類獨(dú)有的方法,只有當(dāng)子類有對(duì)父類的方法重寫時(shí),才調(diào)用子類重寫后的方法?。?!
其二:方法參數(shù)的傳遞:
其三:方法返回值的傳遞
向上轉(zhuǎn)型的最大好處就是——參數(shù)統(tǒng)一化,父類引用可以接收子類所有對(duì)象
看下面這個(gè)例子:
完整代碼為:
public class Animal { public String name; public Animal(String a){ name = a; } public void eat(){ System.out.println("食物"); } public static void fun(Animal animal){ animal.eat(); } } public class Dog extends Animal{ public Dog(String name){ super(name); } public void eat(){ System.out.println("骨頭"); } } public class Cat extends Animal{ public Cat(String name){ super(name); } public void eat(){ System.out.println("魚"); } } public class Test { public static void main(String[] args) { Dog dog = new Dog("豆豆"); Cat cat = new Cat("花花"); dog.fun(dog); cat.fun(cat); } }
結(jié)果為:
拆開(kāi)來(lái)分析:
fun方法的參數(shù)為Animal類的實(shí)例化對(duì)象
Animal的子類對(duì)象,可以直接傳入
也就是說(shuō),對(duì)于Animal類的所有子類實(shí)例化對(duì)象,均可以直接向fun方法傳參,避免了重復(fù)性的寫諸如 public static void fun(Dod d){}, public static void fun(Cat a){}等方法
(2)向下轉(zhuǎn)型:向上轉(zhuǎn)型是子類向父類轉(zhuǎn),向下轉(zhuǎn)型則是將轉(zhuǎn)為的父類還原為子類,這里用 還原 這個(gè)詞是因?yàn)槟芟蛳罗D(zhuǎn)型的前提是:先發(fā)生向上轉(zhuǎn)型且我們需要使用子類獨(dú)有的方法時(shí),才使用向下轉(zhuǎn)型,也很好理解,父類不一定是子類,只有由子類轉(zhuǎn)成的才可以向下轉(zhuǎn)型還原,向下轉(zhuǎn)型的形式如下:
Animal animal = new Dog("豆豆"); Dog dog = (Dog)animal;
基本類似與強(qiáng)制類型轉(zhuǎn)換
注意:向下轉(zhuǎn)型是有風(fēng)險(xiǎn)的,可能無(wú)法強(qiáng)制轉(zhuǎn)換成功,這里可以引用instanceof類,用if語(yǔ)句判斷,避免報(bào)錯(cuò)
if(dog instanceof Dog){ }
(3)方法重寫的幾點(diǎn)注意要求:
- 只能覆寫成員方法,不能重寫static靜態(tài)方法,但是方法重載是可以重載static方法的
- 子類進(jìn)行方法重寫,子類方法的權(quán)限修飾符>=父類方法的權(quán)限修飾符,同時(shí),子類并不能覆寫父類的private方法,對(duì)于父類的包訪問(wèn)權(quán)限修飾方法,在不同包下的子類也不能覆寫
- 用final修飾的方法也不能覆寫哦(JDK中的String類就是一個(gè)final類)
- 返回值必須相同,或是向上轉(zhuǎn)型的,即覆寫的方法的返回值可以是父類方法返回類型的子類
- 注意方法覆寫與方法重載的區(qū)別,方法覆寫:子類與父類的方法名一樣,參數(shù)、返回值類型均一樣,如果只返回值類型不一樣編譯會(huì)報(bào)錯(cuò)
- 可使用@override 檢驗(yàn)方法覆寫是否一樣
(4)最后是一道例題,容易掉坑:
public class A { public A(){ this.func(); } public void func(){ System.out.println("A"); } } class B extends A{ private int num; public B(int num){ this.num = num; } public void func(){ System.out.println("B的num==" + num); } public static void main(String[] args) { B b = new B(100); b.func(); } }
分析運(yùn)行后會(huì)輸出什么呢?
仔細(xì)想想,小心掉坑,答案在文章末尾給出
3.抽象類
對(duì)于方法的覆寫,一般的繼承關(guān)系下,子類是可以選擇覆寫也可以選擇不覆寫的,但在一些場(chǎng)景下,我們想對(duì)子類作出強(qiáng)制性覆寫要求,這就引出了抽象類的概念
(1)抽象類用abstract修飾,抽象類是普通類的超集,它只是在普通類的基礎(chǔ)上多了抽象方法,抽象方法是沒(méi)有方法體的,形式如下:
(2)抽象類必須有子類繼承
(3)抽象類無(wú)法實(shí)例化對(duì)象,僅能用子類new相應(yīng)的對(duì)象
(4)普通子類繼承抽象類,必須覆寫所有的抽象方法,當(dāng)子類仍為抽象類時(shí),可以選擇不覆寫,依舊保留抽象方法
(5)abstract修飾符不能和final同時(shí)使用,也不能和private同時(shí)使用
4.接口
上面講的抽象類雖然能實(shí)現(xiàn)方法的覆寫,但還是有缺陷的,比如抽象類還是遵循單繼承原則,一個(gè)類也只能繼承一個(gè)抽象類,同時(shí),在語(yǔ)義上,只要繼承,就是A is B 的意思,有時(shí)候并不符合邏輯,故而又引出了接口這個(gè)概念
(1)接口的定義與使用
我們用關(guān)鍵字interface來(lái)定義接口,子類用關(guān)鍵字 implements來(lái)實(shí)現(xiàn)接口,同時(shí),通常,在命名接口時(shí),我們會(huì)用大寫的字母“I”開(kāi)頭命名以示區(qū)別,如下面一段代碼的接口名為 IMessage ,而對(duì)于實(shí)現(xiàn)接口的子類命名我們通常用Impl作后綴
(2)接口的特點(diǎn):
接口中只有全局常量和抽象方法(JDK8之前,JDK8又?jǐn)U展了default方法,了解即可),如:
public interface IMessage { public static final int a = 10; public abstract void print(); }
接口中只有public權(quán)限,且全部為全局常量和抽象方法,故而,在接口內(nèi),public、static、final、abstract可以省略不寫,默認(rèn)即為這些關(guān)鍵字,故上一段代碼可以直接寫成下面這段:
public interface IMessage { int a = 10; void print(); }
接口是沒(méi)有單繼承限制的,子類可以implements多個(gè)父接口,父接口之間用逗號(hào)隔開(kāi),如:
public class CImpl implements IB,IMessage{ public void print(){ } public void printf(){ }}
- 同時(shí),接口之間也可以多繼承,一個(gè)接口可以extends多個(gè)父接口
- 接口同抽象類一樣,是不能直接實(shí)例化對(duì)象的,必須通過(guò)實(shí)現(xiàn)它的子類進(jìn)行實(shí)例化
- 如果一個(gè)子類既有繼承的父類,也有實(shí)現(xiàn)的接口,則先繼承父類再實(shí)現(xiàn)父接口
(3)常用的JDK內(nèi)置的兩大接口
a:Comparable接口
當(dāng)使用Arrays.sort()方法排序時(shí),當(dāng)排序?qū)ο鬄樽远x的類時(shí),sort方法不知道應(yīng)該按照對(duì)象的什么屬性進(jìn)行排序,故而待排序的自定義類需實(shí)現(xiàn)該接口,并將抽象方法compareTo覆寫,形式如下:
import java.util.Arrays; public class Person implements Comparable<Person> { // 兩個(gè)屬性,name和age private String name; private int age; // 有參構(gòu)造 public Person(String name,int age){ this.name = name; this.age = age; } // 定義輸出 public String toString(){ return name + "的年齡是" + age; } // 覆寫compareTo方法 public int compareTo(Person o){ return (this.age - o.age); } public static void main(String[] args) { Person p1 = new Person("言希",18); Person p2 = new Person("溫衡",16); Person p3 = new Person("思莞",17); Person []p = new Person[]{p1,p2,p3}; // 用sort方法排序 Arrays.sort(p); System.out.println(Arrays.toString(p)); } }
輸出結(jié)果(按年齡升序):
b: Cloneable接口
Cloneable接口位于java.lang包中,顧名思義,就是用于克隆,在代碼中也就是復(fù)制新的對(duì)象,新對(duì)象的屬性方法都是從原對(duì)象中拷貝過(guò)來(lái)的,在實(shí)現(xiàn)該接口時(shí),只需要覆寫Object類提供的clone方法,如下面示例:
//實(shí)現(xiàn)Cloneable接口 public class Animall implements Cloneable{ private String name; // clone方法 protected Animall clone() throws CloneNotSupportedException { return (Animall)super.clone(); } public static void main(String[] args) throws CloneNotSupportedException{ Animall a1 = new Animall(); a1.name = "豆豆"; // a2由a1克隆而來(lái) Animall a2 = a1.clone(); // 輸出a2,和a1一致 System.out.println(a2.name); // 但是a1并不是a2 System.out.println(a1 == a2); } }
結(jié)果如下:
補(bǔ)充:
- Cloneable接口是標(biāo)記接口,即它本身并沒(méi)有任何抽象方法,當(dāng)一個(gè)類實(shí)現(xiàn)了該接口,就表示該類具備了克隆能力
- clone方法的源代碼為protected native object clone() throws CloneNotSupportedException;,其中native也是一個(gè)關(guān)鍵字,表明是本地方法,即調(diào)用了C++的同名方法故而,我們還可以發(fā)現(xiàn),native修飾的方法也是沒(méi)有方法體的
- 沒(méi)有方法體的一定是抽象方法 ⅹ錯(cuò)誤,因?yàn)閚ative方法也是沒(méi)有方法體的
好了,多態(tài)的內(nèi)容基本就是這么多了,java中的多態(tài)主要依賴于繼承和方法覆寫,而對(duì)于那些需要強(qiáng)制性覆寫的方法,我們又引出了抽象類,再鑒于抽象類有局限,我們又學(xué)習(xí)了接口,整體上內(nèi)容就是這么多了,注意的細(xì)節(jié)比較多,多敲代碼理解理解更棒。
最后,將文中那道例題答案奉上:
答案解析來(lái)啦
從main方法開(kāi)始,執(zhí)行的第一句為 B b = new B(100);,因?yàn)锽繼承自A,故而執(zhí)行B 的構(gòu)造方法要先去執(zhí)行A的構(gòu)造方法,public A(){ this.func(); },這里注意,雖然是A的構(gòu)造,但對(duì)象是B的,故這里的this,func()實(shí)際是B.func(),public void func(){System.out.println(“B的num==” + num);}
因?yàn)檫@里其實(shí)還沒(méi)給num賦值成功,所以num現(xiàn)在還是默認(rèn)值0,所以輸出了答案的第一句 B的num == 0,然后接著執(zhí)行B的構(gòu)造方法,將100賦值給了num,最后執(zhí)行b.func,也就輸出了答案的第二句 B的num ==100
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java利用剪貼板實(shí)現(xiàn)交換程序間數(shù)據(jù)的方法
這篇文章主要介紹了Java利用剪貼板實(shí)現(xiàn)交換程序間數(shù)據(jù)的方法,需要的朋友可以參考下2014-07-07Springmvc ajax跨域請(qǐng)求處理方法實(shí)例詳解
這篇文章主要介紹了Springmvc ajax跨域請(qǐng)求處理方法實(shí)例詳解,需要的朋友可以參考下2017-10-10SpringBoot 下的 Static 文件夾打包成前端資源的示例代碼
這篇文章主要介紹了SpringBoot 下的 Static 文件夾如何打包成前端資源,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06MyBatis-Plus非表字段的三種處理方法小結(jié)
這篇文章主要介紹了MyBatis-Plus非表字段的三種處理方法小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08SpringBoot如何整合Springsecurity實(shí)現(xiàn)數(shù)據(jù)庫(kù)登錄及權(quán)限控制
這篇文章主要給大家介紹了關(guān)于SpringBoot如何整合Springsecurity實(shí)現(xiàn)數(shù)據(jù)庫(kù)登錄及權(quán)限控制的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01SpringBoot個(gè)性化啟動(dòng)Banner設(shè)置方法解析
這篇文章主要介紹了SpringBoot個(gè)性化啟動(dòng)Banner設(shè)置方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03在idea中使用JaCoCo插件統(tǒng)計(jì)單元測(cè)試覆蓋率的實(shí)現(xiàn)
這篇文章主要介紹了在idea中使用JaCoCo插件統(tǒng)計(jì)單元測(cè)試覆蓋率的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01JAVA進(jìn)階篇之詳細(xì)了解File文件的常用API
這篇文章主要給大家介紹了關(guān)于JAVA進(jìn)階篇之詳細(xì)了解File文件的常用API的相關(guān)資料,File用于表示文件系統(tǒng)中的一個(gè)文件或目錄,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11