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