深入講解Java中的多態(tài)和抽象類
1、多態(tài)
- 同一行為,通過不同事物,可以體現(xiàn)不同的形態(tài)。Java是強類型靜態(tài)語言。
- 強類型:每個變量在聲明之前必須聲明它確切的類型
- 靜態(tài):賦值和運算時都是嚴格按照聲明時的數(shù)據(jù)類型來處理的
- 有時候,設(shè)計一個數(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.調(diào)用時按照編譯時方法調(diào)用,運行方法時按照運行時類型運行
2.能調(diào)用什么類型看等號左邊有定義什么方法,子類獨有的方法不能被調(diào)用
3.調(diào)用了重寫方法時,調(diào)用的是父類的方法,執(zhí)行時入棧的是子類重寫過的方法
*/
Dog dog = (Dog)pet;
dog.work();//正確,先進行強轉(zhuǎn),然后調(diào)用子類獨有的方法//訪問屬性時,默認訪問的是引用類型的屬性 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)建的是子類對象
//等號左邊是編譯時類型,等號右邊是運行時類型
//調(diào)用時按照編譯時方法調(diào)用,運行方法時按照運行時類型運行
//能調(diào)用什么類型看等號左邊有定義什么方法,子類獨有的方法不能被調(diào)用
//調(diào)用了重寫方法時,調(diào)用的是父類的方法,執(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)下的轉(zhuǎn)型
@Test
public void test(){
//實際在堆中創(chuàng)建的對象類型是運行時類型,也就是Dog類對象
Pet pet = new Dog();//將子類對象的引用類型從子類類型自動提升成父類類型,稱為向上轉(zhuǎn)型
Cat cat = (Cat)pet;//編譯不報錯,運行時報錯ClassCastException
Dog dog = (Dog)pet;//強制類型轉(zhuǎn)換,將父類引用類型轉(zhuǎn)成子類引用類型,向下轉(zhuǎn)型
}- 向上轉(zhuǎn)型:自動轉(zhuǎn)換,將子類對象的引用類型從子類類型自動提升成父類類型
- 向下轉(zhuǎn)型:強制轉(zhuǎn)換,將父類引用類型轉(zhuǎn)成子類引用類型
- 向下轉(zhuǎn)型要注意的問題:需要使用
instanceof關(guān)鍵字判斷引用對象是否為某一類的對象
- 向下轉(zhuǎn)型要注意的問題:需要使用
@Test
public void test(){
//實際在堆中創(chuàng)建的對象類型是運行時類型,也就是Dog類對象
Pet pet = new Dog();//將子類對象的引用類型從子類類型自動提升成父類類型,稱為向上轉(zhuǎn)型
Cat cat = (Cat)pet;//編譯不報錯,運行時報錯ClassCastException
Dog dog = (Dog)pet;//強制類型轉(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ù):方法的形參用父類類型,傳入的實參用子類類型
- 多態(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 非虛方法
方法在編譯期,就確認了具體的調(diào)用版本,在運行時不可變,這種方法就稱為非虛方法
- 靜態(tài)方法:與類型直接關(guān)聯(lián)
- 私有方法:在外部不可訪問
- final修飾的方法:不能被繼承
- 實例構(gòu)造器(構(gòu)造方法),通過super調(diào)用的父類方法
1.5.2 虛方法
- 靜態(tài)分派:使用父類的引用調(diào)用方法
- 動態(tài)綁定:根據(jù)具體的運行時類型決定運行哪個方法
- 方法的參數(shù)在編譯期間確定,根據(jù)編譯器時類型,找最匹配的
- 方法的所有者如果沒有重寫,就按照編譯時類型處理;如果有重寫,就按照運行時類型處理
1.5.3重寫和重載中的方法調(diào)用
- 重寫示例1
/*
1、編譯期間進行靜態(tài)分派:即確定是調(diào)用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)分派:即確定是調(diào)用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)分派:即確定是分別調(diào)用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)分派:即確定是分別調(diào)用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關(guān)鍵字修飾類,定義的就是抽象方法
- abstract關(guān)鍵字可以修飾類和方法
- abstract關(guān)鍵字修飾的方法特點
- 只能在抽象類中
- 沒有方法體,不能執(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)建的對象可以調(diào)用抽象類的普通方法
//當繼承體系中抽象類存在相同方法簽名的重名方法時,采用就近原則
//不管創(chuàng)建的是哪個子類的對象,都是從現(xià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)的根類,即所有類的父類。
每個類都使用 Object 作為超類。
- Object類型的變量與除Object以外的任意引用數(shù)據(jù)類型的對象都多態(tài)引用
- 所有對象(包括數(shù)組)都實現(xiàn)這個類的方法。
- 如果一個類沒有特別指定父類,那么默認則繼承自O(shè)bject類
3.1 Object類中的API
API(Application Programming Interface),應(yīng)用程序編程接口。
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(對象),默認會自動調(diào)用這個對象的toString()
因為Java的引用數(shù)據(jù)類型的變量中存儲的實際上時對象的內(nèi)存地址,但是Java對程序員隱藏內(nèi)存地址信息,所以不能直接將內(nèi)存地址顯示出來,所以當你打印對象時,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():獲取對象的運行時類型
因為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():用于最終清理內(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()的理解?
- 當對象被GC確定為要被回收的垃圾,在回收之前由GC幫你調(diào)用這個方法,不是由程序員手動調(diào)用。
- 這個方法與C語言的析構(gòu)函數(shù)不同,C語言的析構(gòu)函數(shù)被調(diào)用,那么對象一定被銷毀,內(nèi)存被回收,而finalize方法的調(diào)用不一定會銷毀當前對象,因為可能在finalize()中出現(xiàn)了讓當前對象“復(fù)活”的代碼
- 每一個對象的finalize方法只會被調(diào)用一次。
- 子類可以選擇重寫,一般用于徹底釋放一些資源對象,而且這些資源對象往往時通過C/C++等代碼申請的資源內(nèi)存
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:如果兩個對象調(diào)用equals返回true,那么要求這兩個對象的hashCode值一定是相等的;
? b:如果兩個對象的hashCode值不同的,那么要求這個兩個對象調(diào)用equals方法一定是false;
? c:如果兩個對象的hashCode值相同的,那么這個兩個對象調(diào)用equals可能是true,也可能是false
2.如果重寫equals,那么一定要遵循如下幾個原則:
? a:自反性:x.equals(x)返回true
? b:傳遞性:x.equals(y)為true, y.equals(z)為true,然后x.equals(z)也應(yīng)該為true
? c:一致性:只要參與equals比較的屬性值沒有修改,那么無論何時調(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)文章
AsyncConfigurerSupport自定義異步線程池處理異常
這篇文章主要為大家介紹了AsyncConfigurerSupport自定義異步線程池處理異常詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06
Spring?Boot監(jiān)控SQL運行情況的全過程
這篇文章主要給大家介紹了關(guān)于Spring?Boot監(jiān)控SQL運行情況的相關(guān)資料,文中通過實例代碼介紹的非常詳細,對大家學習或者使用SpringBoot具有一定的參考學習價值,需要的朋友可以參考下2022-02-02
Socket+JDBC+IO實現(xiàn)Java文件上傳下載器DEMO詳解
這篇文章主要介紹了Socket+JDBC+IO實現(xiàn)Java文件上傳下載器DEMO詳解,需要的朋友可以參考下2017-05-05
Java實現(xiàn)字符串的分割(基于String.split()方法)
Java中的我們可以利用split把字符串按照指定的分割符進行分割,然后返回字符串數(shù)組,下面這篇文章主要給大家介紹了關(guān)于Java實現(xiàn)字符串的分割的相關(guān)資料,是基于jDK1.8版本中的String.split()方法,需要的朋友可以參考下2022-09-09
Java使用HttpUtils實現(xiàn)發(fā)送HTTP請求
這篇文章主要介紹了Java使用HttpUtils實現(xiàn)發(fā)送HTTP請求,HTTP請求,在日常開發(fā)中,還是比較常見的,今天給大家分享HttpUtils如何使用,需要的朋友可以參考下2023-05-05
Java web實現(xiàn)賬號單一登錄,防止同一賬號重復(fù)登錄(踢人效果)
這篇文章主要介紹了Java web實現(xiàn)賬號單一登錄,防止同一賬號重復(fù)登錄,有點類似于qq登錄踢人效果,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-10-10

