Java基礎知識回顧之三 ----- 封裝、繼承和多態

來源:互聯網
上載者:User

標籤:前言   overload   修改   問題   sim   成員方法   show   csdn   調用   

前言

在上一篇中回顧了java的修飾符和String類,這篇就來回顧下Java的三大特性:封裝、繼承、多態。

封裝什麼是封裝

在物件導向程式設計方法中,封裝是指一種將抽象性函式介面的實現細節部份封裝、隱藏起來的方法。

封裝可以被認為是一個保護屏障,防止該類的代碼和資料被外部類定義的代碼隨機訪問。要訪問該類的代碼和資料,必須通過嚴格的介面控制。

封裝最主要的功能在於我們能修改自己的實現代碼,而不用修改那些調用我們代碼的程式片段。適當的封裝可以讓程式碼更容易理解與維護,也加強了程式碼的安全性。

簡單的來說,就是將Java中的經常用到的代碼進行封裝起來,形成一個方法。比如,我們常用的實體類,使用private修飾變數,用於保護資料;對外提供getter和setter方法,用於調用。這就是一種典型的封裝。

程式碼範例:

    public class packagingTest {            public static void main(String[] args) {            User user=new User();      //這裡會報錯,因為id和name是私人的,用於保護該資料    //      user.id=10;    //      user.name="張三";            user.setId(1);            user.setName("張三");            System.out.println(user.getId());            System.out.println(user.getName());        }        }        class User{        private int id;        private String name;        public int getId() {            return id;        }        public void setId(int id) {            this.id = id;        }        public String getName() {            return name;        }        public void setName(String name) {            this.name = name;        }    }    

運行結果:

    1    張三
使用封裝的好處
  1. 良好的封裝能夠減少耦合。

  2. 類內部的結構可以自由修改。

  3. 可以對成員變數進行更精確的控制。

  4. 隱藏資訊,實現細節。

繼承什麼是繼承

繼承是java物件導向編程技術的一塊基石,因為它允許建立分等級層次的類。
繼承就是子類繼承父類的特徵和行為,使得子類對象(執行個體)具有父類的執行個體域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。

繼承的特性
  • 子類擁有父類非private的屬性,方法。
  • 子類可以擁有自己的屬性和方法,即子類可以對父類進行擴充。
  • 子類可以用自己的方式實現父類的方法。
為什麼使用繼承

繼承主要目的是為了複用代碼!簡單的來說,就是將重複的代碼抽出來,放到父類中,然後在再由子類繼承使用,子類也是可以對父類進行擴充的。所以在繼承關係中,可以這麼理解,父類更通用,子類更具體。

打個比方,在動物世界中,貓和獅子是屬於貓科,狗和狼是屬於犬科。而它們也都是動物。貓和獅子有個共同的父類貓科,貓和狗有個共同的父類動物。所以它們是符合繼承關係的, 只不過它們在行為上有所區別。貓和狗都有吃和睡,不過貓可以爬樹,狗不可以。
這裡,我們可以使用如下代碼來進行說明。

程式碼範例:

public class extendTest {    public static void main(String[] args) {        Cat cat=new Cat();        Dog dog=new Dog();        cat.eat();        cat.sleep("cat");        cat.climbTree();        dog.eat("dog");        dog.sleep("dog");    }}class  Animal{    public void eat(String name){        System.out.println(name+"正在吃東西...");    }    public void sleep(String name){        System.out.println(name+"正在睡覺...");    }}class Cat extends Animal{    private String name="Cat";    public void eat(){        super.eat(name);        System.out.println(name+"吃完了");    }    public void sleep(){        this.sleep(name);    }        public void sleep(String name){        System.out.println(name+"剛剛睡覺!");    }        public void climbTree(){        System.out.println(name+"正在爬樹!");    }}class Dog extends Animal{    }

運行結果:

Cat正在吃東西...Cat吃完了cat剛剛睡覺!Cat正在爬樹!dog正在吃東西...dog正在睡覺...

在上述代碼中,父類Animal實現了eat和sleep的方法,子類Cat和Dog使用了extends 關鍵字繼承了父類Animal。子類Dog繼承父類Animal之後什麼都沒做,而子類Cat不但繼承了父類Animal,而且還增加了climbTree 方法,並且也重寫了父類的eat和sleep方法。
在子類Cat中,出現了兩個關鍵字:superthis
這兩個關鍵字的意義如下:

  • super關鍵字:實現對父類成員的訪問,用來引用當前對象的父類。
  • this關鍵字:指向自己的引用。

在上述代碼中,子類Cat使用super關鍵字調用了父類Animal的eat方法,使用this關鍵字調用本類中的sleep方法。

說到繼承,就不得不提這幾個東西: final和protected修飾符、構造器、以及向上轉型!
其中final和protected修飾符在上一篇java的修飾符和String類中已經講解了,這裡就簡單的描述下。

  • final:修飾的類不可以被繼承。
  • protected:修飾的類僅對同一包內的類和所有子類可見。
構造器

雖然子類可以繼承父類的屬性和方法(private修飾的除外),但是還有一樣是子類無法繼承的,那就是是構造器!對於構造器而言,它只能夠被調用,而不能被繼承。如果子類想使用父類的構造器,那麼只需使用super關鍵字調用即可。

注:如果父類的構造器被private所修飾,那麼是無法被外部調用的,包括子類!

向上轉型

將子類轉換成父類,在繼承關係上面是向上移動的,所以一般稱之為向上轉型。
之前的個例子中,貓和動物是屬於繼承關係,那麼我們可以把貓當作動物就是向上轉型!
例如:

public class extendTest {    public static void main(String[] args) {        Animal animal=new Cat();        animal.eat("cat");        animal.sleep("cat");    }}class  Animal{    public void eat(String name){        System.out.println(name+"正在吃東西...");    }    public void sleep(String name){        System.out.println(name+"正在睡覺...");    }}class Cat extends Animal{private String name="Cat";    public void eat(){        super.eat(name);        System.out.println(name+"吃完了");    }    public void sleep(){        this.sleep(name);    }        public void sleep(String name){        System.out.println(name+"剛剛睡覺!");    }        public void climbTree(){        System.out.println(name+"正在爬樹!");    }}

運行結果:

cat正在吃東西...cat剛剛睡覺!

上述代碼中完成了向上轉型,但是在向上轉型中是存在著一些缺憾的,那就是屬性和方法的丟失。例如上述代碼中就丟失了Cat類中的climbTree()方法和name屬性。所以慎用向上轉型!

多重繼承

Java的繼承是單繼承,但是可以實現多重繼承!
雖然一個子類只能繼承一個父類,但是子類的子類也可以子類。
例如C類繼承B類,B類繼承A類,所以按照關係就是A類是B類的父類,B類是C類的父類。

繼承的缺點

雖然繼承大大提升了代碼的複用性,但是也提高了類之間的耦合性!父類更改,子類就必須更改!因此可以說繼承破壞了封裝,因為對於父類而言,它的實現細節對與子類來說都是透明的。
所以慎用繼承!!!

多態 什麼是多態

多態是指事物在運行過程中存在不同的狀態。

多態就是指程式中定義的引用變數所指向的具體類型和通過該引用變數發出的方法調用在編程時並不確定,而是在程式運行期間才確定。

使用多態的必要條件

多態存在的三個必要條件:

  1. 要有繼承關係;
  2. 子類要重寫父類的方法;
  3. 父類引用指向子類對象,也就是向上轉型。
多態使用的簡單樣本

在上面的繼承講解中,我們用到了貓和動物這兩個之間的關係,這裡我們也可以使用這,只需要稍微改下代碼,就可以實現多態。

程式碼範例:

public class Test {    public static void main(String[] args) {        Animal animal=new Cat();        animal.eat();    }}class  Animal{    private String name="Animal";    public void eat(){        System.out.println(name+"正在吃東西...");        sleep();    }    public void sleep(){        System.out.println(name+"正在睡覺...");    }}class Cat extends Animal{    private String name="Cat";    public void eat(String name){        System.out.println(name+"吃完了");        sleep();    }    public void sleep(){        System.out.println(name+"正在睡覺");    }}

輸出結果:

Animal正在吃東西...Cat正在睡覺

看到了運行結果之後,如果不熟悉多態的話,是不是感覺有些奇怪呢?
列印的第一句應該好理解,為什麼列印的第二句不是Animal的方法,而是Cat中的方法呢?

我們知道多態是指事物在運行過程中存在不同的狀態。而這裡,我們用到了繼承、重寫以及向上轉型。
在這裡順便提一下:

在向上轉型中,一個父類的引用是可以指向多種子類對象,那麼在運行時對於同一個訊息是由實際的被引用的對象的類型來決定。

根據上述這段理解,我們再來看剛剛的那個樣本。
在Cat類中,重寫了父類Animal的sleep方法,並重載了eat方法。重載之後的eat(String name)方法和父類Animal的eat()方法不是同一個方法,因為是會在向上轉型丟失的。而Cat子類重寫了sleep方法,因此在向上轉型的時候是不會丟失的,並且因為指定對對象的參考型別是Cat,所以Animal在調用eat()方法的時候,先是調用本類中eat()方法,然後在調用子類中的sleep()方法!

結論:

當父類引用指向子類方法時,必須調用那些父類中存在的方法,如果子類中對該方法進行了重寫,那麼在運行時就會動態調用子類中的方法,這就是多態。

使用多態的優點

摘自:https://www.cnblogs.com/jack204/archive/2012/10/29/2745150.html

  1. 可替換性(substitutability)。多態對已存在代碼具有可替換性。例如,多態對圓Circle類工作,對其他任何圓形幾何體,如圓環,也同樣工作。
  2. 可擴充性(extensibility)。多態對代碼具有可擴充性。增加新的子類不影響已存在類的多態性、繼承性,以及其他特性的運行和操作。實際上新加子類更容易獲得多態功能。例如,在實現了圓錐、半圓錐以及半球體的多態基礎上,很容易增添球體類的多態性。
  3. 介面性(interface-ability)。多態是超類通過方法簽名,向子類提供了一個共同介面,由子類來完善或者覆蓋它而實現的。
  4. 靈活性(flexibility)。它在應用中體現了靈活多樣的操作,提高了使用效率。
  5. 簡化性(simplicity)。多態簡化對應用軟體的代碼編寫和修改過程,尤其在處理大量對象的運算和操作時,這個特點尤為突出和重要。

在對多態有一定的認識之後,可以嘗試看看如下代碼。
這是一個經典的多態問題,摘自:
http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx

程式碼範例:

    public class extendsTest {      public static void main(String[] args) {          A a1 = new A();          A a2 = new B();          B b = new B();          C c = new C();          D d = new D();            System.out.println("1--" + a1.show(b));      System.out.println("2--" + a1.show(c));      System.out.println("3--" + a1.show(d));       System.out.println("4--" + a2.show(b));       System.out.println("5--" + a2.show(c));      System.out.println("6--" + a2.show(d));      System.out.println("7--" + b.show(b));    System.out.println("8--" + b.show(c));    System.out.println("9--" + b.show(d));     }      }         class A {      public String show(D obj) {      return ("A and D");      }            public String show(A obj) {      return ("A and A");      }             }             class B extends A{      public String show(B obj){      return ("B and B");      }            public String show(A obj){      return ("B and A");      }       }             class C extends B{            }             class D extends B{           }  

運行結果:

1--A and A2--A and A3--A and D4--B and A5--B and A6--A and D7--B and B8--B and B9--A and D

分析

①②③比較好理解,一般不會出錯。④⑤就有點糊塗了,為什麼輸出的不是"B and B”呢?!!先來回顧一下多態性。

運行時多態性是物件導向程式設計代碼重用的一個最強大機制,動態性的概念也可以被說成“一個介面,多個方法”。Java實現運行時多態性的基礎是動態方法調度,它是一種在運行時而不是在編譯期調用重載方法的機制。

方法的重寫Overriding和重載Overloading是Java多態性的不同表現。

重寫Overriding是父類與子類之間多態性的一種表現,重載Overloading是一個類中多態性的一種表現。如果在子類中定義某方法與其父類有相同的名稱和參數,我們說該方法被重寫(Overriding)。子類的對象使用這個方法時,將調用子類中的定義,對它而言,父類中的定義如同被“屏蔽”了。如果在一個類中定義了多個同名的方法,它們或有不同的參數個數或有不同的參數類型,則稱為方法的重載(Overloading)。Overloaded的方法是可以改變傳回值的類型。

當超類對象引用變數引用子類對象時,被引用對象的類型而不是引用變數的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。(但是如果強制把超類轉換成子類的話,就可以調用子類中新添加而超類沒有的方法了。)

好了,先溫習到這裡,言歸正傳!實際上這裡涉及方法調用的優先問題 ,優先順序由高到低依次為:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。讓我們來看看它是怎麼工作的。

比如④,a2.show(b),a2是一個引用變數,類型為A,則this為a2,b是B的一個執行個體,於是它到類A裡面找show(B obj)方法,沒有找到,於是到A的super(超類)找,而A沒有超類,因此轉到第三優先順序this.show((super)O),this仍然是a2,這裡O為B,(super)O即(super)B即A,因此它到類A裡面找show(A obj)的方法,類A有這個方法,但是由於a2引用的是類B的一個對象,B覆蓋了A的show(A obj)方法,因此最終鎖定到類B的show(Aobj),輸出為"B and A”。

再比如⑧,b.show(c),b是一個引用變數,類型為B,則this為b,c是C的一個執行個體,於是它到類B找show(C obj)方法,沒有找到,轉而到B的超類A裡面找,A裡面也沒有,因此也轉到第三優先順序this.show((super)O),this為b,O為C,(super)O即(super)C即B,因此它到B裡面找show(Bobj)方法,找到了,由於b引用的是類B的一個對象,因此直接鎖定到類B的show(B obj),輸出為"B and B”。

按照上面的方法,可以正確得到其他的結果。
問題還要繼續,現在我們再來看上面的分析過程是怎麼體現出藍色字型那句話的內涵的。它說:當超類對象引用變數引用子類對象時,被引用對象的類型而不是引用變數的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。還是拿a2.show(b)來說吧。

a2是一個引用變數,類型為A,它引用的是B的一個對象,因此這句話的意思是由B來決定調用的是哪個方法。因此應該調用B的show(B obj)從而輸出"B and B”才對。但是為什麼跟前面的分析得到的結果不相符呢?!問題在於我們不要忽略了藍色字型的後半部分,那裡特別指明:這個被調用的方法必須是在超類中定義過的,也就是被子類覆蓋的方法。

B裡面的show(B obj)在超類A中有定義嗎?沒有!那就更談不上被覆蓋了。實際上這句話隱藏了一條資訊:它仍然是按照方法調用的優先順序來確定的。它在類A中找到了show(Aobj),如果子類B沒有覆蓋show(A obj)方法,那麼它就調用A的show(Aobj)(由於B繼承A,雖然沒有覆蓋這個方法,但從超類A那裡繼承了這個方法,從某種意義上說,還是由B確定調用的方法,只是方法是在A中實現而已);現在子類B覆蓋了show(A obj),因此它最終鎖定到B的show(A obj)。這就是那句話的意義所在。

其它

參考:
http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx
https://www.cnblogs.com/jack204/archive/2012/10/29/2745150.html
12786385

到此,本文就結束了,謝謝閱讀!歡迎留言和點贊,你的支援是我寫作最大的動力!
著作權聲明:
虛無境
部落格園出處:http://www.cnblogs.com/xuwujing
CSDN出處:http://blog.csdn.net/qazwsxpcm    
個人部落格出處:http://www.panchengming.com

Java基礎知識回顧之三 ----- 封裝、繼承和多態

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.