1. 程式人生 > >Java總結 - 封裝繼承多型

Java總結 - 封裝繼承多型

  • 我還是一個沒有參加工作的小白,所以這篇文章只是一些自己的理解,如有錯誤請及時指正

面向物件

  • java實體類中包含什麼呢? 屬性,設值器(構造器,get/set方法),eauals()方法和hashcode()方法,目前只能想到這麼多,然後規劃一下:屬性(面向物件部分說),屬性(類之間的關係(面向物件部分說)),設值器(封裝部分說),eauals()方法和hashcode()方法(面向物件部分說)
  • 都在流傳萬物皆物件,那麼怎麼理解呢? 比如說 拿你的基友說這個問題
  • 類中屬性 : 拿你的基友說這個問題,那麼你的基友就可以理解為這裡所說的物件,我問你基友的資訊(年齡啊之類的),那麼就相當於物件中的資訊,所以不管你能想到的任何事情,比如書,電腦,杯子等等物品,都有它的"引數",那麼一個物品的物品名字就可以理解為物件名,對應到java中Class Name,那麼這個物品名的實體就可以成為一個物件了,對應到java中就是你new ClassName()

    了,請注意的是這是兩個不同的概念,你對別人說我的杯子怎麼怎麼樣,他只能想到一個大概的杯子形狀,這個時候就只是java class的層面,而你將你喝水的杯子拿給一個人的時候,那麼這個人就非常清楚明白的看到這個杯子的"引數了",比如說顏色大小,這個時候就對應到了java中的new ClassName()了,這個時候,你在跟他說你的杯子,他就已經知道你的杯子的具體樣子了,他就已經get到了杯子中的屬性並將這個杯子具體化了

    //這就是你所說的杯子,你把這個拿給你朋友,你朋友只知道你的杯子有顏色重量高度等一些資訊,
    //但是啥顏色,多重多高是不知道的
    public class Cup {    
        public String color;
        public float weight;
        public float height;
        //....
    }
    //******************************
    //如果你將這個cup物件拿給你朋友,你朋友就很清楚了,是紅色的杯子,多高多重
    Cup cup = new Cup();
    cup.color = "red";
    cup.weight = 20.6F;
    cup.height = 100.55F;
  • 類之間的引用(關係): 首先明確一點,你和你基友都是屬於人,所以你們自然是一個"類",你問你基友他朋友的事,這就是一種關聯,你基友是一個物件,你基友的朋友自然也就是一個物件,你問你基友,然後你基友再問他的朋友,所以這裡形成的關聯就是你->你基友->你基友的朋友,你們之間有一種關係紐帶,就是你們之間的關係,(再比如說你跟你老爸,是父子關係),對應到java中也就是類之間的引用

    //大家都是人...
    public class Person {
        //這些屬性就是每個人都有的屬性,你叫啥,多大,年齡等等
        public String name;
        public String gender ;
        public int age;
        //這就是代表人和人之間的關係
        Person yourFriend;
    }
    public static void main(String[] args) {
        //你自己
        Person own = new Person();
        own.name = "wangziqiang";
        own.gender = "男";
        own.age = 20;
        //你基友
        Person yourFriend = new Person();
        yourFriend.name = "xiaoer";
        yourFriend.gender = "男";
        yourFriend.age = 20;
        //你和你基友之間建立關係引用
        own.yourFriend = yourFriend;
        //你基友的朋友
        Person other = new Person();
        other.name = "zhangfei";
        other.gender = "男";
        other.age = 50;
        //你基友和你基友的朋友建立關係引用
        yourFriend.yourFriend = other;
        //你通過你基友問他朋友的年齡,發現你基友有個忘年交
        int age = own.yourFriend.yourFriend.age;
    }
  • 人和人區分:人和人咋區分呢?可以通過長相,身份證,關係都可以,那麼對應到類中,我們比如說上面的Person,我們可以通過name來區分,但是單單用name可不行,天底下多少叫張三的啊,那不就瘋了?? ,所以我們區分一個人要將很多資訊彙總在一起來區分他,比如說有兩個名字一樣的,但是可以通過身份證區分啊等等,在類中我們就可以用eauals()方法和hashcode()方法來綜合區分,但是還是需要注意兩個方法不得不同的,比如

    @Override
    public boolean equals(Object o) {
        //根據你的三個引數區分你
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name) &&
                Objects.equals(gender, person.gender);
    }
    @Override
    public int hashCode() {
        //根據你的三個引數區分你
        return Objects.hash(name, gender, age);
    }
    • 如上是我們自己實現的,但是在Object中實現的equals是直接用==來判斷兩個物件是否相等的,==操作是直接以兩個物件的地址作為判斷條件的,而hashCode也是返回一個物件的地址,當你用Object中的hashCode方法比較兩個物件的時候其實也是比較的兩個物件的地址,在這也可以反映出來,java實現的這兩個方法不管使用哪一個都可以返回相同的比較結果,所以我們也應該遵守這個規則,如果我們在重寫equals時就一定要重寫hashcode,以保證比較狀態的一致性,如果不這樣,就會造成程式的錯誤,以至於HashMap和HashSet工作不正常,我們也可以使用AutoValue框架和lombok框架可以用來自動生成類的toString和hashCode的一類的方法
  • 到這裡我把能想到的類中的概念都解釋了一下,下面就是面向物件思想中涉及到的封裝,繼承,多型了

封裝

  • 封裝意思很明確就是將一個東西包起來,如上的PersonCup類,只要某個類一動手,直接把人家性別的給改了,這是不允許的,所以封裝的意圖就在於為了隱藏類中的部分屬性,以避免可以直接被其他類隨意訪問

1

  • 上面個圖片自己感覺就很生動形象了,只給其他人留一個小視窗,通過視窗給我的我可以接著,但是你要送個炸彈,我還能給你扔出去,而不是如下這樣,任冰雨在臉上胡亂的拍~,如果你這時候在房子裡,就不會這樣

2

  • 那麼對應到類中我們就需要改變一下上面的類,拿Person開刀

    public class Person {
        //加private
        private String name;
        private String gender ;
        private int age;
        private Person yourFriend;
        //get/set方法
    }
  • 但是不是所有情況都是加private就可以了,如果存在繼承的情況,那麼就需要按情況來了,這個之後再說,當修改完了Person類之後,我們就應該這樣使用了

    public static void main(String[] args) {
        Person own = new Person();
        own.setName("wangziqiang");
        own.setGender("男");
        own.setAge(20);
    
        Person yourFriend = new Person();
        yourFriend.setName("xiaoer");
        yourFriend.setGender("男");
        yourFriend.setAge(20);
        own.setYourFriend(yourFriend);
    
        Person other = new Person();
        other.setName("zhangfei");
        other.setGender("男");
        other.setAge(50);
        yourFriend.setYourFriend(other);
    
        int age = own.getYourFriend().getYourFriend().getAge();
    }
  • 程式碼是多了,但是保護類的效果是非常顯著的,上面是都實現了每個屬性的set/get方法,但是好像還是可以隨意更改啊,如果你要控制一個屬性,對屬性對應的get/set方法下手就了,比如我們的性別一生都不變,那麼我們可以直接在構造器中初始化好,然後將對應的setGender()方法取消掉就好了,具體的程式碼就不展示了
  • 對於構造器程式碼塊,比如如下,假如我們只生產紅杯子

    public class Cup {
        public String color;
        public float weight;
        public float height;
        //構造程式碼塊
        {
            this.color = "red";
        }
        public Cup(float weight, float height) {
            this.weight = weight;
            this.height = height;
        }
    }
  • 我們知道構造程式碼塊是優先於構造器的,其實現原理無非就是將構造程式碼塊中的程式碼在編譯完後直接賦值給變數,我們可以使用相關工具檢視編譯好的class類

    public class Cups {
        public String color;
        public float weight;
        public float height;
        //構造程式碼塊
        {
            this.color = "red";
        }
        public Cups(float weight, float height) {
            this.weight = weight;
            this.height = height;
        }
    }
  • 檢視

    public class Cup
    {
      public String color = "red";
      public float weight;
      public float height;
    
      public Cup(float weight, float height)
      {
        this.weight = weight;
        this.height = height;
      }
    }
  • 但是注意像IO初始化有異常的這種,會被提到構造器中

    public class Person {
        //加private
        private String name;
        private String gender ;
        private int age;
        private FileInputStream in;
        {
            try {
                this.in = new FileInputStream("x");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        public Person(String name, String gender, int age) {
            this.name = name;
            this.gender = gender;
            this.age = age;
        }
    }
  • 檢視

    public class Person
    {
      private String name;
      private String gender;
      private int age;
      private FileInputStream in;
      public Person(String name, String gender, int age)
      {
        try
        {
          this.in = new FileInputStream("x");
        }
        catch (FileNotFoundException e)
        {
          e.printStackTrace();
        }
        this.name = name;
        this.gender = gender;
        this.age = age;
      }
    }
  • 那麼之前說的你給我炸彈我可以給你扔出去呢 ??

    public class Person {
        private String name;
        private String gender;
        private int age;
        public String getGender() {
            return gender;
        }
        public void setGender(String gender) {
            if ("nan".equals(gender)){
                //我只接受我是男的
                this.gender = gender;
            }else {
                //你要改我的性別,你去死吧,不操作代表不接受
            }
        }
        //other getter/setter
    }
  • 當然上面的邏輯判斷可能不太好,但是隻是用來說明問題的,這裡面可以加邏輯判斷
  • 至此,我能想到的東西就完全的敘述完了

繼承

  • 繼承東西就比較多了,比如先了解一下基本使用

    public class Father {
        private String name;
        private int age;
        protected long money;
    
        @Override
        public String toString() {
            return "Father{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", money=" + money +
                    '}';
        }
    }
    public class Son extends Father {
        private String name;
        private int age;
    
        public Son(String name, int age) {
            this.name = name;
            this.age = age;
        }
        @Override
        public String toString() {
            return "Son{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", money=" + money +
                    '}';
        }
    }
  • 如上就可以反映出,在Son中我們並沒有money的屬性,完全來自於父類,所以繼承可以使子類獲取父類的特定的屬性和方法,所以繼承就可以實現類的可複用即減少可重複程式碼的作用
  • 但是需要注意的是final修飾的類是不允許被繼承的,private修飾的屬性和方法也是隻有父類擁有的,而對於final修飾的父類方法,子類是不可重寫的
  • 上面注意到money被子類繼承下來了,注意他的修飾符是protected,所以現在我們需要說一下修飾關鍵字

    • private :私有的,修飾方法和屬性都屬於父類本身,子類是不可見的
    • protected :只允許一個類的子類繼承實現自己的方法和屬性
    • default:這個不是關鍵字,只是代表一種訪問級別,即一個類或方法不加任何修飾的時候,是包內的類可以訪問的
    • public:公開的,即誰都可以訪問
  • 上面說的訪問控制修飾符都是針對外部類來說的,對於類自己來說,類中可以隨意訪問,排除私有內部類
  • 例項化的時候就會涉及到類的初始化,那麼java中是從一個類的頂層類開始初始化,直到自己初始化完成才可以,我們可以看一下,

    public class Father {
        String other = "other";
        private String name = "a";
        private static int age = 2;
        private static String gender = "n";
        public Father() {
            System.out.println("father");
        }
    }
    public class Son extends Father {
        private String name = "s";
        private static int age = 2;
        public Son() {
            System.out.println("son");
        }
        public static void main(String[] args) {
            Son son = new Son();
        }
    }
    /**
     * father
     * son
     */

  • 如上圖是在main方法中斷點時看到的,當son初始化完畢,控制檯會輸出如上註釋內容,也證實了是先初始化父類,然後才是自己,當然Father是繼承Object的,只是Object沒輸出,然後我們注意到圖片上的內容

    • 靜態區:父類的private static都被繼承了下來
    • son物件內:父類的private也被繼承了下來,當然其他比private訪問許可權大的也會被繼承下來
  • 所以到這我們就可以總結一下,子類擁對父類的私有變數具有擁有權,但是不具有使用權,如果想有使用權,可以提供get/set
  • 對於靜態區,static修飾的變數都是類本身的,所以如片中的並不是son中擁有的,即子類不會繼承父類的static變數,這是我自己的認為的.如有不對請指正
  • 所以到這,可以說如果子類的靜態變數和父類中的靜態變數重名了,這樣是不屬於重寫的,只是各自擁有而已
  • 好了到這,我還沒想起來其他需要注意的,下面要說的就是父子之間的轉換問題
  • 首先記住,不存在父子關係是不能夠轉換的
  • 如果父類中有三個變數,而子類中比父類多好多變數,那麼在強轉為父類的時候,這些變數是不能夠使用了,就好像是一個大貨車過限高杆,會被削掉一部分,那麼這樣的操作在java中不允許的,這可以看做是向下轉型,比如這樣的不允許

    Father father = new Father();
    System.out.println(father);
    Son castSon = (Son) father;   //ClassCastException
    System.out.println(castSon);
  • 但是這樣的向下轉型是可以的,因為雖然是父類的引用,但依舊是子類的型別

    Father father = new Son();
    System.out.println(father);
    System.out.println(father.getClass());  //Son
    Son castSon = (Son) father;
    System.out.println(castSon);
  • 允許的是向上轉型,即子類向父類轉

    Son son = new Son();
    System.out.println(son);
    Father castSon = son;
    System.out.println(castSon);
  • 使用繼承雖然好,但是還是需要注意一些問題,最大壞處就是封裝性的破壞,這裡涉及到一個詞:組合

    • 組合的意思很明瞭,就像是拼積木,所以我們是該用哪一種呢 ?繼承的父子類之間的關係是is-a,而組合是has-a,繼承即cat是animal,組合就是leg,eyes組成animal,所以如果你實現的邏輯是什麼是什麼,那麼就用繼承,如果是某些東西組成一個什麼,就使用組合
  • 到這我能想到的東西就沒了,下面將是多型的介紹

多型

  • 其實上面的轉型就是一種多型,但是放到繼承也好像合理點,不管放哪裡,現在你已經知道了的是多型可以進行轉型
  • 上面提到的限高杆問題,只是針對真實型別的父類不可以轉型為其子類,因為會丟掉東西,但是如下這樣因為存在繼承關係,會自動的向上轉型,但依然會丟掉一些東西,即father雖然真實型別是Son,但是隻能使用Father內的東西,而Son中對Father擴充套件的其他類就不可以了,

    Father father = new Son();
  • 看一個例子,繼承關係圖是這樣的

markdown_img_paste_20190109150045298

public class A {
    public String show(D obj) {
        return ("A and D");
    }
    public String show(A obj) {
        return ("A and A");
    }
}
public class B extends A{
    public String show(B obj){
        return ("B and B");
    }    
    public String show(A obj){
        return ("B and A");
    }
}
public class C extends B{
}
public class D extends B{
}
public class Test {
    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));      
    }
}
output:  
  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
  • 上面這道題還是有點意思的,首先我們做這道題要知道的是具體的實現看子類就可以了,下面我們具體來看一下怎麼會輸出這些東西
  • 首先方法呼叫的優先順序為

    this.show(O)
    super.show(O)
    this.show((super)O)
    super.show((super)O)
  • 好了按照上面這個順序,我們開始做一個a1.show(b),首先a1型別為A,所以this代表A,其實現也為A,然後在類A中尋找引數為B的方法,發現沒有,然後去找A的父類中的show方法,因為A沒有父類,排除Object,所以進行第三個判斷,(super)O代表的是(super)B,所以這裡在A中尋找引數為A的方法,發現有此方法,,然後判斷a1物件有沒有子類實現,沒有,所以直接就輸出1--A and A,可以這樣表示

    • 在A類中搜-> A.show(B),未發現
    • 在Object類中搜-> Object.show(B),未發現
    • 在A類中搜-> A.show((super) B) -> A.show(A),發現有此方法,然後判斷真實型別,發現是A,然後A類的實現決定最後實現
  • 我們再來看5的過程a2.show(c),a2的this為A,即定義變數的型別

    • 在A類中搜 -> A.show(C),未發現
    • 在Object類中搜-> Object.show(C),未發現
    • 在A類中搜-> A.show(super C) -> A.show(B),未發現,此時引數B型別還有父類,往上找,即搜->A.show(B super) -> A.show(A),發現此方法,然後檢視具體實現類,發現是B類,在B類中找到重寫的方法,此方法決定了最終實現,輸出5--B and A
  • 所以到這通過一個案例基本說明了多型是如何使用的,記住一個物件的具體實現還要看其真實實現類~
  • 多型機制遵循的原則概括為:當父類物件引用變數引用子類物件時,被引用物件的型別而不是引用變數的型別決定了呼叫誰的成員方法,但是這個被呼叫的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法,但是它仍然要根據繼承鏈中方法呼叫的優先順序來確認方法,自我理解:不對請多指正,就是用誰定義的此變數,那麼就決定了呼叫誰中的成員方法,但是前提是被呼叫的方法是在父類中定義過的,當執行程式碼的時候,如果實現類中實現了父類中的方法,那麼會執行實現類中重寫後的方法