天天看點

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的一類的方法
  • 到這裡我把能想到的類中的概念都解釋了一下,下面就是面向對象思想中涉及到的封裝,繼承,多态了

封裝

  • 封裝意思很明确就是将一個東西包起來,如上的

    Person

    Cup

    類,隻要某個類一動手,直接把人家性别的給改了,這是不允許的,是以封裝的意圖就在于為了隐藏類中的部分屬性,以避免可以直接被其他類随意通路,包裝起來後更利于一個類的内聚
Java總結 - 封裝繼承多态面向對象封裝繼承多态
  • 上面個圖檔自己感覺就很生動形象了,隻給其他人留一個小視窗,通過視窗給我的我可以接着,但是你要送個炸彈,我還能給你扔出去,而不是如下這樣,任冰雨在臉上胡亂的拍~,如果你這時候在房子裡,就不會這樣
Java總結 - 封裝繼承多态面向對象封裝繼承多态
  • 那麼對應到類中我們就需要改變一下上面的類,拿

    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
     */           
Java總結 - 封裝繼承多态面向對象封裝繼承多态
  • 如上圖是在

    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();           
  • 看一個例子,繼承關系圖是這樣的
Java總結 - 封裝繼承多态面向對象封裝繼承多态
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.show((super) B) -> A.show(A)

      ,發現有此方法,然後判斷真實類型,發現是A,然後A類的實作決定最後實作
  • 我們再來看5的過程

    a2.show(c)

    ,a2的this為A,即定義變量的類型
    • 在A類中搜 ->

      A.show(C)

    • Object.show(C)

    • A.show(super C) -> A.show(B)

      ,未發現,此時參數B類型還有父類,往上找,即搜->

      A.show(B super) -> A.show(A)

      ,發現此方法,然後檢視具體實作類,發現是B類,在B類中找到重寫的方法,此方法決定了最終實作,輸出

      5--B and A

  • 是以到這通過一個案例基本說明了多态是如何使用的,記住一個對象的具體實作還要看其真實實作類~
  • 多态機制遵循的原則概括為:當父類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆寫的方法,但是它仍然要根據繼承鍊中方法調用的優先級來确認方法,自我了解:不對請多指正,就是用誰定義的此變量,那麼就決定了調用誰中的成員方法,但是前提是被調用的方法是在父類中定義過的,當執行代碼的時候,如果實作類中實作了父類中的方法,那麼會執行實作類中重寫後的方法