1. 程式人生 > >Java基礎知識回顧之三 ----- 封裝、繼承和多態

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

get flex 防止 應用 需要 當前 nim lex aging

前言

在上一篇中回顧了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 A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--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
https://blog.csdn.net/chenssy/article/details/12786385

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

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