面向對象三大特點——封裝、繼承和多態

學過面向對象的我們都知道,面向對象有三大特點爲——封裝、繼承和多態。

封裝

之前在介紹函數時我們說一個函數就是封裝的體現,封裝就是將一類事物進行封裝成一個類,或者封裝具有獨立功能的代碼塊,封裝的好處就是明確了內外,內部的東西外部無需知道,只要創建一個類的實例對象進行調用其中的方法即可。目前我們所見的封裝體現有:循環、函數、類、 private(用該關鍵詞修飾的函數和對外界是不可被訪問的,且也不會被繼承,被修飾的成員變量對外界也是不被訪問的,需要創建相應的修改器和訪問器對成員函數進行修改和訪問)

繼承

繼承是類與類之間的關係,當我們在定義若干類的時候發現,這些類有共同的屬性和行爲,則可以將這些共同的屬性和行爲單獨抽取出來,放到另一個類中,那麼這個類可以稱之爲其他類的父類,而其他類稱之爲子類。父類和子類之間是繼承關係用“extends”表現,即:

表示:
    class 父類{

    }
    class 子類 extends 父類{

    }

注意:繼承必須是同種類且符合常理的,代碼上的體現僅僅是屬性和行爲重複(前提)

class Demo01{
    public static void main(String[] args){
        Worker w=new Worker();
        System.out.println(w.age);
        w.eat();
    }
}

class Person{  //定義一個person類作爲不同人的類型的父類
    int age;   //每個人都有一個年齡
    String name;  //每個人也都有相應的姓名
    Person(){     //默認的無參構造函數

    }

    Person(String name){    //帶姓名的參數的構造函數
        System.out.println("Person construction");
    }

    void eat(){  //每個人都要喫飯
        System.out.println("三餐");
    }
}

class Worker extends Person{    //工人屬於人類一種類型,可繼承person類中的屬性
    Worker(){
        super();  //表示調用父類的無參構造函數,如果這裏不寫表示隱藏,但必須在第一句
        System.out.println("Worker construction");
    }
    
    void work(){}  //工人有他自己的行爲是工作
}

class Student extends Person{  //學生也屬於人類一種類型,也可繼承person類中的屬性
    Student(){  //學生的構造函數,這裏沒有super()不代表就沒有調用,而是隱藏了
       System.out.println("Student construction");
    }
    void study(){}  //學生有他自己的行爲是學習
}

如上代碼中,我們看到工人有姓名、年齡等屬性和喫行爲,學生也有這些屬性和行爲,如果分別在工人類和學生類中寫的話,也會造成代碼的冗餘,因此我可以將公共屬性抽取出來重新定義一個符合常理的類就是person類,那麼用繼承則兩個子類就都可以訪問到父類中的屬性了。其中我們看到子類在調用構造函數創建對象時,構造函數有一個super()方法,這個方法是調用父類的構造函數,就算不寫也會有一個隱藏的這個方法,那麼爲什麼要調用父類的構造函數呢?

是因爲在繼承父類的屬性時,我們並不是將父類的屬性都移動或者複製到子類中,而是通過對象去訪問父類中的屬性和方法,也就是說子類中並沒這些屬性和方法而是可以訪問到這些方法和屬性,因此在子類創建對象的時候要對相應的成員變量進行默認初始化,那麼父類也要做相應的初始化爲子類提供後續的操作

那麼之前介紹構造函數的時候說到,調用構造函數就意味着創建了對象,那麼這裏子類創建一個對象意味着父類也要跟着創建一個對象嗎?顯然不是的,這裏只是爲子類提供成員變量的初始化,並沒有創建父類的對象,所有除非new一個父類的對象,否則子類創建它的對象是不會影響到父類是否創建對象的。

在前面我們說到,繼承要符合常理,現在我們來看這樣一段代碼:

public class  Dog{

   int age;    //狗的年齡屬性
   String name;   //狗的名字屬性
   String type;   //狗的類型屬性
     
   void eat(){};   //狗喫的行爲
   void speak(){   //狗叫的行爲
     System.out.println("汪汪汪") 
    };
 }

這裏定義了一個Dog類,它也有相應的名字和年齡屬性和喫的行爲,從代碼上我們也可以讓它繼承person類,但是繼承是要符合常理的,人類不可能生出狗來啊!!!so,這裏可以定義一個Animal類將這些屬性和行爲抽取出來,讓Dog類啊,Cat類啊等等去繼承它,這就很符合常理了。

繼承的特性:

1.單繼承和繼承體系

       繼承相當於就是父親和孩子的關係,一個父親可能有多個孩子,但是一個孩子僅有唯一的一個父親,那麼由此說明,在Java的繼承體系中,只能單繼承一個子類只可繼承一個父類,但是一個父類可以被多個子類去繼承,如A繼承B,C繼承B,此時A不能繼承C或者其他類。那麼如果我們即想要A訪問到B中的屬性和方法,又想要A訪問到C中的屬性和方法怎麼辦呢?在Java中是有繼承體系這一說的,相當於人類的“族譜”,即C繼承B,A再繼承C這樣是可以的。因此要記住,繼承之中是is a 的關係

2.子父類中構造函數的特點
      子類在創建對象的時候會調用父類的相對應的構造函數,因此super()必須在第一句可以默認不寫,但是我們說在表示當前對象時,this()必須在第一句,那麼這兩個關鍵字的調用不就衝突了?顯然不是的,在前面介紹構造函數時,說構造函數之間不能相互調用,所以至少有一個構造函數第一句不是this(),這樣就不會衝突了。如果一旦父類沒有無參的構造函數,那麼super()就不能用了,應該顯示調用相應的代參的super(...)方法。默認無參構造函數第一句默認是super()
       爲什麼要調用父類構造函數呢?是爲子類的屬性(繼承而來的)進行初始化,但是不代表父類創建了對象。
       父類的構造函數如果被private私有化了,則子類無法創建對象 只能是繼承;如果一個類的所有構造函數都private 意味着子類不能創建對象,一般還需要在這個類之前加final 表示真正的不能被繼承,因此我們今後在創建類的時候 最好將無參構造函數寫出來

3.子父類中成員變量的特點:
    查找變量的順序:1).在函數當中找(局部變量)
                                 2).在對象當中找(成員變量)
                                 3).在該類的靜態方法區找(靜態變量)
                                 4).在父類空間裏找(父類的成員變量)
                                 5).在父類的靜態方法區找(父類的靜態變量)

   子類在沒有定義要調用的屬性時,會自動調用父類的該屬性
    如果子父類中有重名屬性,則優先調用子類的

4.子父類中成員函數的特點:
     子類對象調用函數先調用自己,再調用父類
     如果子父類中存在函數重名的情況爲函數的重寫,至於子類怎麼解決,都是子類的問題,子類是指拿到了父類中的函數聲明。
     子類中的成員函數可以基於父類的內容進行升級和降級
     如果父類的函數已經被private私有化了,則子類不能重寫;重寫函數,子類的權限必須大於等於父類的權限,子類的返回值和參數列表必須和父類一樣。

5.所有類沒有明顯的繼承關係,都有一個最終的父類爲object,這個類是所有類的最終父類,它沒有構造函數(可以翻看源碼)

6.super()和this()

   this表示當前對象的引用
   super(...) 調用父類相對應的代參構造函數
   this(...)  調用當前類中相對應的構造函數
   super.XXX super.XXX() 調用父類的屬性或函數
   this.XXX this.XXX()   調用當前對象的屬性或函數

多態

多態體現的前提要有繼承,狀態的自動轉換隻能向上轉,可以向下轉(但必須進行強制類型轉換),即同一個事物在不同的場合具有不同的形態
多態的最常用的體現是父類的引用指向子類對象,如:

class Demo01{
    public static void main(String[] args){
        Dog dog=new Dog();  //狗類創建一個它自己dog對象
        Cat cat=new Cat();    //貓類創建一個它自己cat的對象
        dog.eat();      //狗類對象調用自己的eat方法
        dog.lookHome();  //狗類對象調用自己的lookHome方法

     //多態的體現

        Animal a1=dog;    //創建一個父類的引用將子類的對象引用給它,這裏給的就是子類對象指向的地址
        Animal a2=cat;
        a1.eat();     //這裏父類調用的是父類自己的eat方法,而不是子類中的eat方法,它無法訪問到子類中的屬性和方法
       // a1.lookHome();  這裏是錯誤的調用方式

       // Cat cat2=(Cat)a1;  這裏不被允許,是因爲a1現在還是一個狗的對象,將它強制轉換爲貓類對象是不符合道理的,當然在程序中也不允許這樣寫
        Cat cat2=(Cat)a2;  //將父類引用強制轉換爲相應的子類對象引用,前面要加相應的強制轉換類
    }
}
class Animal{    //創建一個動物類的父類對象
    public abstract void eat(){};
    public void sleep(){
        System.out.println("閉眼睡覺....");
    }
}
class Cat extends Animal{  //定義一個貓類讓它繼承動物類
    public void  eat(){
        System.out.println("喫魚....");
    }
    public void catchJerry(){
        System.out.println("捉老鼠....");
    }
}
class Dog extends Animal{  //定義一個狗類讓它繼承動物類
    public void  eat(){
        System.out.println("喫骨頭....");
    }
    public void lookHome(){
        System.out.println("看家....");
    }
}

多態的特點:

1.多態中成員變量的特點
    父類只能訪問父類中的成員變量和函數不能訪問子類中的屬性和函數

2.多態中成員函數的特點
    如果子類沒有重寫父類的函數,則父類的引用也不能調用子類的函數
    子類當中的函數有:繼承而來的函數, 繼承重寫的函數, 以及特有的函數
    因此父類可以調用繼承而來和繼承重寫的函數,但是唯獨不能調用子類特有的函數

至此我們的面向對象三大特點就介紹完了,希望對你有所幫助!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章