深入講解Java中的多態(tài)和抽象類
1、多態(tài)
- 同一行為,通過不同事物,可以體現(xiàn)不同的形態(tài)。Java是強(qiáng)類型靜態(tài)語言。
- 強(qiáng)類型:每個(gè)變量在聲明之前必須聲明它確切的類型
- 靜態(tài):賦值和運(yùn)算時(shí)都是嚴(yán)格按照聲明時(shí)的數(shù)據(jù)類型來處理的
- 有時(shí)候,設(shè)計(jì)一個(gè)數(shù)組或方法的參數(shù),返回值類型時(shí),無法確定具體的類型,只能確定是某個(gè)系列的類型,這時(shí)就引入了多態(tài)
1.1 多態(tài)的實(shí)現(xiàn)方式
父類的引用,創(chuàng)建子類對象。必須有繼承,父類定義方法,子類重寫方法。
- 方法的形參用父類類型,傳入的實(shí)參用子類類型
- 使用父類類型聲明對象,創(chuàng)建的是子類對象
class Pet{ public String name="pet"; public void speak(){ } } class Dog extends Pet{ public String name="dog"; @Override public void speak(){ System.out.println("汪汪汪"); } public void work(){ } } class Cat extends Pet{ public String name="cat"; @Override public void speak(){ System.out.println("喵喵喵"); } } class Master{ public void speak(Pet pet){//形參是父類類型對象 pet.speak(); } }
1.2 多態(tài)狀態(tài)下需要注意的問題
Pet pet = new Dog();//使用父類類型聲明對象,創(chuàng)建的是子類對象 //等號左邊是編譯時(shí)類型,等號右邊是運(yùn)行時(shí)類型
pet.play();//編譯和運(yùn)行都正常 //pet.work();//報(bào)錯(cuò) /* 報(bào)錯(cuò)原因 1.調(diào)用時(shí)按照編譯時(shí)方法調(diào)用,運(yùn)行方法時(shí)按照運(yùn)行時(shí)類型運(yùn)行 2.能調(diào)用什么類型看等號左邊有定義什么方法,子類獨(dú)有的方法不能被調(diào)用 3.調(diào)用了重寫方法時(shí),調(diào)用的是父類的方法,執(zhí)行時(shí)入棧的是子類重寫過的方法 */ Dog dog = (Dog)pet; dog.work();//正確,先進(jìn)行強(qiáng)轉(zhuǎn),然后調(diào)用子類獨(dú)有的方法
//訪問屬性時(shí),默認(rèn)訪問的是引用類型的屬性 System.out.println(pet.name);//pet System.out.println(pet1.name);//pet
@Test public void test(){ Master master = new Master(); Pet pet = new Cat();//使用父類類型聲明對象,創(chuàng)建的是子類對象 //等號左邊是編譯時(shí)類型,等號右邊是運(yùn)行時(shí)類型 //調(diào)用時(shí)按照編譯時(shí)方法調(diào)用,運(yùn)行方法時(shí)按照運(yùn)行時(shí)類型運(yùn)行 //能調(diào)用什么類型看等號左邊有定義什么方法,子類獨(dú)有的方法不能被調(diào)用 //調(diào)用了重寫方法時(shí),調(diào)用的是父類的方法,執(zhí)行時(shí)入棧的是子類重寫過的方法 Pet pet1 = new Dog(); master.speak(new Cat());//實(shí)參是子類類型對象 master.speak(new Dog()); //訪問屬性時(shí),默認(rèn)訪問的是引用類型的屬性 System.out.println(pet.name);//pet System.out.println(pet1.name);//pet }
1.3 多態(tài)狀態(tài)下的轉(zhuǎn)型
@Test public void test(){ //實(shí)際在堆中創(chuàng)建的對象類型是運(yùn)行時(shí)類型,也就是Dog類對象 Pet pet = new Dog();//將子類對象的引用類型從子類類型自動提升成父類類型,稱為向上轉(zhuǎn)型 Cat cat = (Cat)pet;//編譯不報(bào)錯(cuò),運(yùn)行時(shí)報(bào)錯(cuò)ClassCastException Dog dog = (Dog)pet;//強(qiáng)制類型轉(zhuǎn)換,將父類引用類型轉(zhuǎn)成子類引用類型,向下轉(zhuǎn)型 }
- 向上轉(zhuǎn)型:自動轉(zhuǎn)換,將子類對象的引用類型從子類類型自動提升成父類類型
- 向下轉(zhuǎn)型:強(qiáng)制轉(zhuǎn)換,將父類引用類型轉(zhuǎn)成子類引用類型
- 向下轉(zhuǎn)型要注意的問題:需要使用
instanceof
關(guān)鍵字判斷引用對象是否為某一類的對象
- 向下轉(zhuǎn)型要注意的問題:需要使用
@Test public void test(){ //實(shí)際在堆中創(chuàng)建的對象類型是運(yùn)行時(shí)類型,也就是Dog類對象 Pet pet = new Dog();//將子類對象的引用類型從子類類型自動提升成父類類型,稱為向上轉(zhuǎn)型 Cat cat = (Cat)pet;//編譯不報(bào)錯(cuò),運(yùn)行時(shí)報(bào)錯(cuò)ClassCastException Dog dog = (Dog)pet;//強(qiáng)制類型轉(zhuǎn)換,將父類引用類型轉(zhuǎn)成子類引用類型,向下轉(zhuǎn)型 System.out.println(pet instanceof Dog);//true System.out.println(pet instanceof Cat);//false System.out.println(pet instanceof Pet);//true }
1.4 多態(tài)的應(yīng)用
- 多態(tài)參數(shù):方法的形參用父類類型,傳入的實(shí)參用子類類型
- 多態(tài)數(shù)組:同一父類的不同子類對象可以組成一個(gè)數(shù)組
@Test public void test(){ Pet[] pets = new Pet[4]; pets[0] = new Cat(); pets[1] = new Dog(); pets[2] = new Dog(); pets[3] = new Cat(); for(int i=0;i<pets.length;i++){ pets[i].play(); } }
1.5 虛方法和非虛方法
只有虛方法才能實(shí)現(xiàn)多態(tài),使用的比較多的虛方法
1.5.1 非虛方法
方法在編譯期,就確認(rèn)了具體的調(diào)用版本,在運(yùn)行時(shí)不可變,這種方法就稱為非虛方法
- 靜態(tài)方法:與類型直接關(guān)聯(lián)
- 私有方法:在外部不可訪問
- final修飾的方法:不能被繼承
- 實(shí)例構(gòu)造器(構(gòu)造方法),通過super調(diào)用的父類方法
1.5.2 虛方法
- 靜態(tài)分派:使用父類的引用調(diào)用方法
- 動態(tài)綁定:根據(jù)具體的運(yùn)行時(shí)類型決定運(yùn)行哪個(gè)方法
- 方法的參數(shù)在編譯期間確定,根據(jù)編譯器時(shí)類型,找最匹配的
- 方法的所有者如果沒有重寫,就按照編譯時(shí)類型處理;如果有重寫,就按照運(yùn)行時(shí)類型處理
1.5.3重寫和重載中的方法調(diào)用
- 重寫示例1
/* 1、編譯期間進(jìn)行靜態(tài)分派:即確定是調(diào)用Animal類中的public void eat()方法,如果Animal類或它的父類中沒有這個(gè)方法,將會報(bào)錯(cuò)。 2、運(yùn)行期間進(jìn)行動態(tài)綁定:即確定執(zhí)行的是Cat類中的public void eat()方法,因?yàn)樽宇愔貙懥薳at()方法,如果沒有重寫,那么還是執(zhí)行Animal類在的eat()方法 */ abstract class Animal { public abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("吃魚"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨頭"); } } public class Test{ public static void main(String[] args){ Animal a = new Cat(); a.eat(); } }
- 重載示例1
class Father{ } class Son extends Father{ } class Daughter extends Father{ } class MyClassOne{ public void method(Father f) { System.out.println("father"); } public void method(Son s) { System.out.println("son"); } public void method(Daughter d) { System.out.println("daughter"); } } class MyClassTwo{ public void method(Father f) { System.out.println("father"); } public void method(Son s) { System.out.println("son"); } } public class TestOverload { /* 1、編譯期間進(jìn)行靜態(tài)分派:即確定是調(diào)用MyClassOne類中的method(Father f)方法。 2、運(yùn)行期間進(jìn)行動態(tài)綁定:確定執(zhí)行的是MyClassOne類中method(Father f)方法 ## 因?yàn)榇藭r(shí)f,s,d編譯時(shí)類型都是Father類型,因此method(Father f)是最合適的 */ @Test public void test() { MyClassOne my = new MyClassOne(); Father f = new Father(); Father s = new Son(); Father d = new Daughter(); my.method(f);//father my.method(s);//father my.method(d);//father } /* 1、編譯期間進(jìn)行靜態(tài)分派:即確定是分別調(diào)用MyClassTwo類中的method(Father f),method(Son s),method(Father f)方法。 2、運(yùn)行期間進(jìn)行動態(tài)綁定:即確定執(zhí)行的是MyClass類中的method(Father f),method(Son s),method(Father f)方法 ## 因?yàn)榇藭r(shí)f,s,d編譯時(shí)類型分別是Father、Son、Daughter,而Daughter只能與Father參數(shù)類型匹配 */ @Test public void test() { MyClassTwo my = new MyClassTwo(); Father f = new Father(); Son s = new Son(); Daughter d = new Daughter(); my.method(f);//father my.method(s);//Son my.method(d);//father } }
重載與重寫示例1
class MyClass{ public void method(Father f) { System.out.println("father"); } public void method(Son s) { System.out.println("son"); } } class MySub extends MyClass{ public void method(Daughter d) { System.out.println("daughter"); } } class Father{ } class Son extends Father{ } class Daughter extends Father{ } /* 1、編譯期間進(jìn)行靜態(tài)分派:即確定是分別調(diào)用MyClass類中的method(Father f),method(Son s),method(Father f)方法。 2、運(yùn)行期間進(jìn)行動態(tài)綁定:即確定執(zhí)行的是MyClass類中的method(Father f),method(Son s),method(Father f)方法。 ## my變量在編譯時(shí)類型是MyClass類型,那么在MyClass類中,只有method(Father f),method(Son s)方法。,s,d變量編譯時(shí)類型分別是Father、Son、Daughter,而Daughter只能與Father參數(shù)類型匹配。而在MySub類中并沒有重寫method(Father f)方法,所以仍然執(zhí)行MyClass類中的method(Father f)方法 */ public class TestOverload { public static void main(String[] args) { MyClass my = new MySub(); Father f = new Father(); Son s = new Son(); Daughter d = new Daughter(); my.method(f);//father my.method(s);//son my.method(d);//father } }
2、抽象類
2.1 定義及特點(diǎn)
使用abstract
關(guān)鍵字修飾類,定義的就是抽象方法
- abstract關(guān)鍵字可以修飾類和方法
- abstract關(guān)鍵字修飾的方法特點(diǎn)
- 只能在抽象類中
- 沒有方法體,不能執(zhí)行
- 抽象類的特點(diǎn)
- 抽象類中可以有抽象方法,也可以有普通方法
- 抽象類不允許創(chuàng)建對象
抽象父類的子類一定要重寫抽象父類的所有抽象方法
2.2 抽象類需要注意的問題
public class TestOne { public static void main(String[] args) { //Person person = new Person();//報(bào)錯(cuò),不允許創(chuàng)建對象 //Person p = new Woman();//報(bào)錯(cuò),不允許創(chuàng)建對象 Person student = new Student();//正確 Woman student1 = new Student();//正確 Student student3 = new Student();//正確 //創(chuàng)建的對象可以調(diào)用抽象類的普通方法 //當(dāng)繼承體系中抽象類存在相同方法簽名的重名方法時(shí),采用就近原則 //不管創(chuàng)建的是哪個(gè)子類的對象,都是從現(xiàn)在當(dāng)前類尋找方法進(jìn)行調(diào)用,然后尋找本類的子父類,然后尋找本類的父類的父類 student.methodOne(); student1.methodOne(); student3.methodOne(); //訪問成員變量,聲明的引用類型是什么類型,就訪問此類型中的成員變量 System.out.println(student.country); System.out.println(student1.country); System.out.println(student3.country); } } abstract class Person{ //抽象類可以有自己的屬性 public String country="person"; //可以有抽象方法 abstract void sayHello(); //可以有普通方法 public void methodOne(){ System.out.println("in abstract class Person methodOne"); } } abstract class Woman extends Person{ public String country="woman"; abstract void sing(); public void methodOne(){ System.out.println("in abstract class Woman methodOne"); } } class Student extends Woman{ public String country="student"; @Override void sayHello() { } @Override void sing() { } public void methodOne(){ System.out.println("in abstract class Student methodOne"); } }
3、Object根父類
類 java.lang.Object
是類層次結(jié)構(gòu)的根類,即所有類的父類。
每個(gè)類都使用 Object
作為超類。
- Object類型的變量與除Object以外的任意引用數(shù)據(jù)類型的對象都多態(tài)引用
- 所有對象(包括數(shù)組)都實(shí)現(xiàn)這個(gè)類的方法。
- 如果一個(gè)類沒有特別指定父類,那么默認(rèn)則繼承自O(shè)bject類
3.1 Object類中的API
API(Application Programming Interface),應(yīng)用程序編程接口。
Java API是一本程序員的字典
,是JDK中提供給我們使用的類的說明文檔。
所以我們可以通過查詢API的方式,來學(xué)習(xí)Java提供的類,并得知如何使用它們。
在API文檔中是無法得知這些類具體是如何實(shí)現(xiàn)的,如果要查看具體實(shí)現(xiàn)代碼,那么我們需要查看src源碼。
根據(jù)JDK源代碼及Object類的API文檔,Object類當(dāng)中包含的方法有11個(gè)。今天我們主要學(xué)習(xí)其中的5個(gè):
3.1.1 toString()
public String toString()
①默認(rèn)情況下,toString()返回的是“對象的運(yùn)行時(shí)類型 @ 對象的hashCode值的十六進(jìn)制形式"
②通常是建議重寫,如果在eclipse中,可以用Alt +Shift + S–>Generate toString()
③如果我們直接System.out.println(對象),默認(rèn)會自動調(diào)用這個(gè)對象的toString()
因?yàn)镴ava的引用數(shù)據(jù)類型的變量中存儲的實(shí)際上時(shí)對象的內(nèi)存地址,但是Java對程序員隱藏內(nèi)存地址信息,所以不能直接將內(nèi)存地址顯示出來,所以當(dāng)你打印對象時(shí),JVM幫你調(diào)用了對象的toString()。
例如自定義的Person類:
public class Person { private String name; private int age; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } // 省略構(gòu)造器與Getter Setter }
3.1.2 getClass()
public final Class<?> getClass():獲取對象的運(yùn)行時(shí)類型
因?yàn)镴ava有多態(tài)現(xiàn)象,所以一個(gè)引用數(shù)據(jù)類型的變量的編譯時(shí)類型與運(yùn)行時(shí)類型可能不一致,因此如果需要查看這個(gè)變量實(shí)際指向的對象的類型,需要用getClass()方法
public static void main(String[] args) { Object obj = new String(); System.out.println(obj.getClass());//運(yùn)行時(shí)類型 }
3.1.3 finalize()
protected void finalize():用于最終清理內(nèi)存的方法
public class TestFinalize { public static void main(String[] args) { for (int i = 0; i < 10; i++) { MyData my = new MyData(); } System.gc();//通知垃圾回收器來回收垃圾 try { Thread.sleep(2000);//等待2秒再結(jié)束main,為了看效果 } catch (InterruptedException e) { e.printStackTrace(); } } } class MyData{ @Override protected void finalize() throws Throwable { System.out.println("輕輕的我走了..."); } }
面試題:對finalize()的理解?
- 當(dāng)對象被GC確定為要被回收的垃圾,在回收之前由GC幫你調(diào)用這個(gè)方法,不是由程序員手動調(diào)用。
- 這個(gè)方法與C語言的析構(gòu)函數(shù)不同,C語言的析構(gòu)函數(shù)被調(diào)用,那么對象一定被銷毀,內(nèi)存被回收,而finalize方法的調(diào)用不一定會銷毀當(dāng)前對象,因?yàn)榭赡茉趂inalize()中出現(xiàn)了讓當(dāng)前對象“復(fù)活”的代碼
- 每一個(gè)對象的finalize方法只會被調(diào)用一次。
- 子類可以選擇重寫,一般用于徹底釋放一些資源對象,而且這些資源對象往往時(shí)通過C/C++等代碼申請的資源內(nèi)存
3.1.4 hashCode()
public int hashCode():返回每個(gè)對象的hash值。
hashCode 的常規(guī)協(xié)定:
①如果兩個(gè)對象的hash值是不同的,那么這兩個(gè)對象一定不相等;
②如果兩個(gè)對象的hash值是相同的,那么這兩個(gè)對象不一定相等。
主要用于后面當(dāng)對象存儲到哈希表等容器中時(shí),為了提高存儲和查詢性能用的。
public static void main(String[] args) { System.out.println("Aa".hashCode());//2112 System.out.println("BB".hashCode());//2112 }
3.1.5 equals()
public boolean equals(Object obj):用于判斷當(dāng)前對象this與指定對象obj是否“相等”
①默認(rèn)情況下,equals方法的實(shí)現(xiàn)等價(jià)于與“==”,比較的是對象的地址值
②我們可以選擇重寫,重寫有些要求:
1.如果重寫equals,那么一定要一起重寫hashCode()方法,因?yàn)橐?guī)定:
? a:如果兩個(gè)對象調(diào)用equals返回true,那么要求這兩個(gè)對象的hashCode值一定是相等的;
? b:如果兩個(gè)對象的hashCode值不同的,那么要求這個(gè)兩個(gè)對象調(diào)用equals方法一定是false;
? c:如果兩個(gè)對象的hashCode值相同的,那么這個(gè)兩個(gè)對象調(diào)用equals可能是true,也可能是false
2.如果重寫equals,那么一定要遵循如下幾個(gè)原則:
? a:自反性:x.equals(x)返回true
? b:傳遞性:x.equals(y)為true, y.equals(z)為true,然后x.equals(z)也應(yīng)該為true
? c:一致性:只要參與equals比較的屬性值沒有修改,那么無論何時(shí)調(diào)用結(jié)果應(yīng)該一致
? d:對稱性:x.equals(y)與y.equals(x)結(jié)果應(yīng)該一樣
? e:非空對象與null的equals一定是false
到此這篇關(guān)于深入講解Java中的多態(tài)和抽象類的文章就介紹到這了,更多相關(guān)Java多態(tài)和抽象類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaWeb入門教程之分頁查詢功能的簡單實(shí)現(xiàn)
這篇文章主要介紹了JavaWeb入門教程之分頁查詢功能的簡單實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11AsyncConfigurerSupport自定義異步線程池處理異常
這篇文章主要為大家介紹了AsyncConfigurerSupport自定義異步線程池處理異常詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Spring?Boot監(jiān)控SQL運(yùn)行情況的全過程
這篇文章主要給大家介紹了關(guān)于Spring?Boot監(jiān)控SQL運(yùn)行情況的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用SpringBoot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02Socket+JDBC+IO實(shí)現(xiàn)Java文件上傳下載器DEMO詳解
這篇文章主要介紹了Socket+JDBC+IO實(shí)現(xiàn)Java文件上傳下載器DEMO詳解,需要的朋友可以參考下2017-05-05Java實(shí)現(xiàn)字符串的分割(基于String.split()方法)
Java中的我們可以利用split把字符串按照指定的分割符進(jìn)行分割,然后返回字符串?dāng)?shù)組,下面這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)字符串的分割的相關(guān)資料,是基于jDK1.8版本中的String.split()方法,需要的朋友可以參考下2022-09-09Java使用HttpUtils實(shí)現(xiàn)發(fā)送HTTP請求
這篇文章主要介紹了Java使用HttpUtils實(shí)現(xiàn)發(fā)送HTTP請求,HTTP請求,在日常開發(fā)中,還是比較常見的,今天給大家分享HttpUtils如何使用,需要的朋友可以參考下2023-05-05Java web實(shí)現(xiàn)賬號單一登錄,防止同一賬號重復(fù)登錄(踢人效果)
這篇文章主要介紹了Java web實(shí)現(xiàn)賬號單一登錄,防止同一賬號重復(fù)登錄,有點(diǎn)類似于qq登錄踢人效果,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10