1. 程式人生 > >Java架構-JavaSE(二)之繼承、封裝、多型

Java架構-JavaSE(二)之繼承、封裝、多型

閱讀目錄(Content)

一、封裝(資料的隱藏)
  1.1、封裝的步驟
  1.2、舉例
  1.3、封裝的作用
  1.4、封裝的意義
二、方法的過載
三、繼承
四、super關鍵字
  4.1、super的使用
  4.2、super使用的注意的地方
  4.3、super 和 this 的區別
五、方法重寫(方法覆蓋)
六、多型
七、instanceof和型別轉換
  7.1、instanceof  
  7.2、型別轉換
學習完類與物件終於認識到什麼是類,什麼是物件了。接下來要看的就是java的三大特徵:繼承、封裝、多型。

一、封裝(資料的隱藏)

在定義一個物件的特性的時候,有必要決定這些特性的可見性,即哪些特性對外部是可見的,哪些特性用於表示內部狀態。
通常,應禁止直接訪問一個物件中資料的實際表示,而應通過操作介面來訪問,這稱為資訊隱藏。
1.1、封裝的步驟
   1).使用private 修飾需要封裝的成員變數。
   2.)提供一個公開的方法設定或者訪問私有的屬性
設定 通過set方法,命名格式: set屬性名(); 屬性的首字母要大寫
訪問 通過get方法,命名格式: get屬性名(); 屬性的首字母要大寫

1.2、舉例
   //物件不僅能再類中方法,還能在類的外部"直接"訪問
   public class Student{
   public String name;
   public void println(){
   System.out.println(this.name);
   }
   }
   public class Test{
   public static void main(String[] args){
   Student s = new Student();
   s.name = “tom”;
   }
   }

在類中一般不會把資料直接暴露在外部的,而使用private(私有)關鍵字把資料隱藏起來
   例如:
   public class Student{
   private String name;
   }

      public class Test{

public static void main(String[] args){
   Student s = new Student();
   //編譯報錯,在類的外部不能直接訪問類中的私有成員
      s.name = “tom”;
   }
   }

如果在類的外部需要訪問這些私有屬性,那麼可以在類中提供對於的get和set方法,以便讓使用者在類的外部可以間接的訪問到私有屬性
   例如:
   //set負責給屬性賦值
   //get負責返回屬性的值
   public class Student{
   private String name;
   public void setName(String name){
    

this.name = name;
   }
   public String getName(){
   return this.name;
   }
   }

      public class Test{

public static void main(String[] args){
   Student s = new Student();
   s.setName(“tom”);
   System.out.println(s.getName());
   }
   }
   1.3、封裝的作用
    1)框架
    2)工具類

1.4、封裝的意義
  1)隱藏程式碼的實現細節
   2)統一使用者的呼叫介面
   3)提高系統的可維護性
二、方法的過載
類中有多個方法,有著相同的方法名,但是方法的引數各不相同,這種情況被稱為方法的過載。
方法的過載可以提供方法呼叫的靈活性。

    例如:System.out.println()中的println方法,為什麼可以把不同型別的引數傳給這個方法?
    例如:
        public class Test{
            public void test(String str){

            }
            public void test(int a){
            }
        }

方法過載必須滿足一下條件:
    1)方法名相同
    2)引數列表不同(引數的型別、個數、順序的不同)
        public void test(Strig str){}
        public void test(int a){}

        public void test(Strig str,double d){}
        public void test(Strig str){}

        public void test(Strig str,double d){}
        public void test(double d,Strig str){}
    3)方法的返回值可以不同,也可以相同。
注:在java中,判斷一個類中的倆個方法是否相同,主要參考倆個方面:方法名字和引數列表

三、繼承
  1)繼承是類和類之間的一種關係
除此之外,類和類之間的關係還有依賴、組合、聚合等。

  2)繼承關係的倆個類,一個為子類(派生類),一個為父類(基類)。
    子類繼承父類,使用關鍵字extends來表示
    例如:
    public class student extends Person{
        
    }

3)子類和父類之間,從意義上講應該具有"is a"的關係.
    例如:
        student is a person
        dog is a animal

4)類和類之間的繼承是單繼承
    一個子類只能"直接"繼承一個父類,就像是一個人只能有一個親生父親
    一個父類可以被多子類繼承,就像一個父親可以有多個孩子

    注:java中介面和介面之間,有可以繼承,並且是多繼承。

5)父類中的屬性和方法可以被子類繼承
    子類中繼承了父類中的屬性和方法後,在子類中能不能直接使用這些屬性和方法,是和這些屬性和方法原有的修飾符(public protected default private)相關的。
        例如:
        父類中的屬性和方法使用public修飾,在子類中繼承後"可以直接"使用
        父類中的屬性和方法使用private修飾,在子類中繼承後"不可以直接"使用
    注:具體細則在修飾符部分詳細說明
    
    父類中的構造器是不能被子類繼承的,但是子類的構造器中,會隱式的呼叫父類中的無參構造器(預設使用super關鍵字)。
    注:具體細節在super關鍵字部分詳細說明

6)Object類
    java中的每一個類都是"直接" 或者 "間接"的繼承了Object類.所以每一個物件都和Object類有"is a"的關係。從API文件中,可以看到任何一個類最上層的父類都是Object。(Object類本身除外)
        AnyClass is a Object
    
    例如:
        System.out.println(任何物件 instanceof Object);
        //輸出結果:true
    注:任何物件也包含陣列物件


    例如:
        //編譯後,Person類會預設繼承Object
        public class Person{}

        //Student是間接的繼承了Object
        public class Student extends Person{}
        

    在Object類中,提供了一些方法被子類繼承,那麼就意味著,在java中,任何一個物件都可以呼叫這些被繼承過來的方法。(因為Object是所以類的父類)
        例如:toString方法、equals方法、getClass方法等
    
    注:Object類中的每一個方法之後都會使用到.

四、super關鍵字
子類繼承父類之後,在子類中可以使用this來表示訪問或呼叫子類中的屬性或方法,使用super就表示訪問或呼叫父類中的屬性和方法。

4.1、super的使用
  1)訪問父類中的屬性
   例如:
   public class Person{
   protected String name = “zs”;
   }
   public class Student extends Person{
   private String name = “lisi”;
   public void tes(String name)t{
   System.out.println(name);
   System.out.println(this.name);
   System.out.println(super.name);
   }
   }

2)呼叫父類中的方法
   例如:
   public class Person{
   public void print(){
   System.out.println(“Person”);
   }
   }
   public class Student extends Person{
   public void print(){
   System.out.println(“Student”);
   }
   public void test(){
   print();
   this.print();
   super.print();
   }
   }

3)呼叫父類中的構造器
   例如:
   public class Person{

      }
      public class Student extends Person{
          //編譯通過,子類構造器中會隱式的呼叫父類的無參構造器
          //super();
          public Student(){
          }
      }

例如:
   public class Person{
   protected String name;
   public Person(String name){
   this.name = name;
   }
   }
   public class Student extends Person{
   //編譯報錯,子類構造器中會隱式的呼叫父類的無參構造器,但是父類中沒有無參構造器
   //super();
   public Student(){
  
   }
   }

例如:
   public class Person{
   protected String name;
   public Person(String name){

          this.name = name;
          }
      }
      public class Student extends Person{
          //編譯通過,子類構造器中顯式的呼叫父類的有參構造器
          public Student(){
              super("tom");
          }
      }    
    注:不管是顯式還是隱式的父類的構造器,super語句一定要出現在子類構造器中第一行程式碼。所以this和super不可能同時使用其呼叫構造器的功能,因為它們都要出現在第一行程式碼位置。

例如:
   public class Person{
   protected String name;
   public Person(String name){
   this.name = name;
   }
   }
   public class Student extends Person{
   //編譯報錯,super呼叫構造器的語句不是第一行程式碼
   public Student(){
   System.out.println(“Student”);
   super(“tom”);
   }
   }

      例如:
      public class Person{
          protected String name;
          public Person(String name){
              this.name = name;
          }

}
   //編譯通過
   public class Student extends Person{
   private int age;
   public Student(){
   this(20);
   }
   public Student(int age){
   super(“tom”);
   this.age = age;
   }
}

4.2、super使用的注意的地方
   1)用super呼叫父類構造方法,必須是構造方法中的第一個語句。
   2)super只能出現在子類的方法或者構造方法中。
   3)super 和 this 不能夠同時呼叫構造方法。(因為this也是在構造方法的第一個語句)

4.3、super 和 this 的區別
   1)代表的事物不一樣:
  this:代表所屬方法的呼叫者物件。
   super:代表父類物件的引用空間。
   2)使用前提不一致:
   this:在非繼承的條件下也可以使用。
  super:只能在繼承的條件下才能使用。
   3)呼叫構造方法:
   this:呼叫本類的構造方法。
   super:呼叫的父類的構造方法
   五、方法重寫(方法覆蓋)
  1)方法重寫只存在於子類和父類(包括直接父類和間接父類)之間。在同一個類中方法只能被過載,不能被重寫.

  2)靜態方法不能重寫
   a. 父類的靜態方法不能被子類重寫為非靜態方法 //編譯出錯
   b. 父類的非靜態方法不能被子類重寫為靜態方法;//編譯出錯
   c. 子類可以定義與父類的靜態方法同名的靜態方法(但是這個不是覆蓋)
    例如:
         A類繼承B類 A和B中都一個相同的靜態方法test
             B a = new A();
             a.test();//呼叫到的是B類中的靜態方法test

             A a = new A();
             a.test();//呼叫到的是A類中的靜態方法test

         可以看出靜態方法的呼叫只和變數宣告的型別相關
         這個和非靜態方法的重寫之後的效果完全不同

3)私有方法不能被子類重寫
    子類繼承父類後,是不能直接訪問父類中的私有方法的,那麼就更談不上重寫了。

    例如:
    public class Person{
        private void run(){}
    }
    //編譯通過,但這不是重寫,只是倆個類中分別有自己的私有方法
    public class Student extends Person{
        private void run(){}
    }

4)重寫的語法
    1.方法名必須相同
    2.引數列表必須相同
    3.訪問控制修飾符可以被擴大,但是不能被縮小
        public protected default private
    4.丟擲異常型別的範圍可以被縮小,但是不能被擴大
        ClassNotFoundException ---> Exception  
    5.返回型別可以相同,也可以不同,如果不同的話,子類重寫後的方法返回型別必須是父類方法返回型別的子型別
        例如:父類方法的返回型別是Person,子類重寫後的返回類可以是Person也可以是Person的子型別

    注:一般情況下,重寫的方法會和父類中的方法的宣告完全保持一致,只有方法的實現不同。(也就是大括號中程式碼不一樣)

    例如:
    public class Person{
        public void run(){}

        protected Object test()throws Exception{
            return null;
        }
    }
    //編譯通過,子類繼承父類,重寫了run和test方法.
    public class Student extends Person{
        public void run(){}

        public String test(){
            return "";
        }
    }
        
5)為什麼要重寫
    子類繼承父類,繼承了父類中的方法,但是父類中的方法並不一定能滿足子類中的功能需要,所以子類中需要把方法進行重寫。

6)總結:

方法重寫的時候,必須存在繼承關係。

方法重寫的時候,方法名和形式引數 必須跟父類是一致的。

方法重寫的時候,子類的許可權修飾符必須要大於或者等於父類的許可權修飾符。( private < protected < public,friendly < public )

方法重寫的時候,子類的返回值型別必須小於或者等於父類的返回值型別。( 子類 < 父類 ) 資料型別沒有明確的上下級關係

方法重寫的時候,子類的異常型別要小於或者等於父類的異常型別。
六、多型
允許不同類的物件對同一訊息做出響應。即同一訊息可以根據傳送物件的不同而採用多種不同的行為方式。
相同類域的不同物件,呼叫相同的方法,執行結果是不同的

1)一個物件的實際型別是確定的
    例如: new Student(); new Person();等

2)可以指向物件的引用的型別有很多
    一個物件的實現型別雖然是確定的,但是這個物件所屬的型別可能有很多種。
    例如: Student繼承了Person類
    Student s1 = new Student();
    Person s2  = new Student();
    Object s3  = new Student();

    因為Person和Object都是Student的父型別

    注:一個物件的實際型別是確定,但是可以指向這個物件的引用的型別,卻是可以是這物件實際型別的任意父型別。
    
3)一個父類引用可以指向它的任何一個子類物件
    例如:
    Object o = new AnyClass();
    Person p = null;
    p = new Student();
    p = new Teacher();
    p = new Person();

4)多型中的方法呼叫
    例如:
    public class Person{
        public void run(){}
    }
    public class Student extends Person{

    }
    //呼叫到的run方法,是Student從Person繼承過來的run方法
    main:
        Person p = new Student();
        p.run();

    例如:
    public class Person{
        public void run(){}
    }
    public class Student extends Person{
        public void run(){
            //重寫run方法
        }
    }
    //呼叫到的run方法,是Student中重寫的run方法
    main:
        Person p = new Student();
        p.run();

    注:子類繼承父類,呼叫a方法,如果a方法在子類中沒有重寫,那麼就是呼叫的是子類繼承父類的a方法,如果重寫了,那麼呼叫的就是重寫之後的方法。

5)子類中獨有方法的呼叫
    例如:
    public class Person{
        public void run(){}
    }
    public class Student extends Person{
        public void test(){
        }
    }
    main:
        Person p = new Student();
        //呼叫到繼承的run方法
        p.run();
        
        //編譯報錯,因為編譯器檢查變數p的型別是Person,但是在Person類中並沒有發現test方法,所以編譯報錯.
        p.test();
    
    注:一個變數x,呼叫一個方法test,編譯器是否能讓其編譯通過,主要是看宣告變數x的型別中有沒有定義test方法,如果有則編譯通過,如果沒有則編譯報錯.而不是看x所指向的物件中有沒有test方法.

原理:編譯看左邊,執行不一定看右邊。
   編譯看左邊的意思:java 編譯器在編譯的時候會檢測引用型別中含有指定的成員,如果沒有就會報錯。子類的成員是特有的,父類的沒有的,所以他是找不到的。

6)子類引用和父類引用指向物件的區別
    Student s = new Student();
    Person p = new Student();
    
    變數s能呼叫的方法是Student中有的方法(包括繼承過來的),變數p能呼叫的方法是Person中有的方法(包括繼承過來的)。

    但是變數p是父型別的,p不僅可以指向Student物件,還可以指向Teacher型別物件等,但是變數s只能指向Studnet型別物件,及Student子型別物件。變數p能指向物件的範圍是比變數s大的。
    
    Object型別的變數o,能指向所有物件,它的範圍最大,但是使用變數o能呼叫到的方法也是最少的,只能呼叫到Object中的宣告的方法,因為變數o宣告的型別就是Object.

    注:java中的方法呼叫,是執行時動態和物件繫結的,不到執行的時候,是不知道到底哪個方法被呼叫的。

7)重寫、過載和多型的關係
    過載是編譯時多型
        呼叫過載的方法,在編譯期間就要確定呼叫的方法是誰,如果不能確定則編譯報錯
    重寫是執行時多型
        呼叫重寫的方法,在執行期間才能確定這個方法到底是哪個物件中的。這個取決於呼叫方法的引用,在執行期間所指向的物件是誰,這個引用指向哪個物件那麼呼叫的就是哪個物件中的方法。(java中的方法呼叫,是執行時動態和物件繫結的)

8)多型的注意事項

多型情況下,父類 和 子類存在同名的成員變數,無論是靜態的還是非靜態的變數,預設訪問的是父類中的成員變數。
多型情況下,父類 和 子類存在同名的非靜態方法,訪問的是子類的非靜態方法。
多型情況下,父類 和子類存在同名的靜態方法,訪問的是父類的靜態方法。
多型情況下,不能訪問子類特有的屬性、方法。
多型滿足的條件:必須要有繼承關係。
  多型情況下,子類 和 父類如果存在同名的成員,訪問的都是父類,除了同名的非靜態變數訪問的才是子類。
  9)多型存在的條件

1)有繼承關係  
    2)子類重寫父類方法  
    3)父類引用指向子類物件
補充一下第二點,既然多型存在必須要有“子類重寫父類方法”這一條件,那麼以下三種類型的方法是沒有辦法表現出多型特性的(因為不能被重寫):

1)static方法,因為被static修飾的方法是屬於類的,而不是屬於例項的
2)final方法,因為被final修飾的方法無法被子類重寫
3)private方法和protected方法,前者是因為被private修飾的方法對子類不可見,後者是因為儘管被protected修飾的方法可以被子類見到,也可以被子類重寫,但是它是無法被外部所引用的,一個不能被外部引用的方法,怎麼能談多型呢

七、instanceof和型別轉換
  7.1、instanceof  
  public class Person{
public void run(){}
}
public class Student extends Person{
}
public class Teacher extends Person{
}

例如:
    main:
        Object o = new Student();
        System.out.println(o instanceof Student);//true
        System.out.println(o instanceof Person);//true
        System.out.println(o instanceof Object);//true
        System.out.println(o instanceof Teacher);//false
        System.out.println(o instanceof String);//false
        ---------------------------
       
        Person o = new Student();
        System.out.println(o instanceof Student);//true
        System.out.println(o instanceof Person);//true
        System.out.println(o instanceof Object);//true
        System.out.println(o instanceof Teacher);//false
        //編譯報錯
        System.out.println(o instanceof String);         
        ---------------------------
        Student o = new Student();
        System.out.println(o instanceof Student);//true
        System.out.println(o instanceof Person);//true
        System.out.println(o instanceof Object);//true
        //編譯報錯
        System.out.println(o instanceof Teacher);
        //編譯報錯
        System.out.println(o instanceof String);

        注1:
            System.out.println(x instanceof Y);
            該程式碼能否編譯通過,主要是看宣告變數x的型別和Y是否存在子父類的關係.有"子父類關"系就編譯通過,沒有子父類關係就是編譯報錯.
            之後學習到的介面型別和這個是有點區別的。
        
        注2:
            System.out.println(x instanceof Y);
            輸出結果是true還是false,主要是看變數x所指向的物件實際型別是不是Y型別的"子型別".

例如:
    main:
        Object o = new Person();
        System.out.println(o instanceof Student);//false
        System.out.println(o instanceof Person);//true
        System.out.println(o instanceof Object);//true
        System.out.println(o instanceof Teacher);//false
        System.out.println(o instanceof String);//false 

7.2、型別轉換
public class Person{
public void run(){}
}
public class Student extends Person{
public void go(){}
}
public class Teacher extends Person{
}

    1)為什麼要型別轉換
        //編譯報錯,因為p宣告的型別Person中沒有go方法
        Person p = new Student();
        p.go();

        //需要把變數p的型別進行轉換
        Person  p = new Student();
        Student s = (Student)p;
        s.go();
        或者
        //注意這種形式前面必須要倆個小括號
        ((Student)p).go();            
        
    2)型別轉換中的問題
        //編譯通過 執行沒問題
        Object o = new Student();
        Person p = (Person)o;

  
        //編譯通過 執行沒問題
        Object o = new Student();
        Student s = (Student)o;
       
        //編譯通過,執行報錯
        Object o = new Teacher();
        Student s = (Student)o;

     
        即: 
            X x = (X)o;
            執行是否報錯,主要是變數o所指向的物件實現型別,是不是X型別的子型別,如果不是則執行就會報錯。
            為什麼某些人會一直比你優秀,是因為他本身就很優秀還一直在持續努力變得更優秀,而你是不是還在滿足於現狀內心在竊喜! 

合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

還是那句話,希望此文能幫到大家的同時,也聽聽大家的觀點。歡迎留言討論,加關注,分享你的高見!持續更新!

To-陌霖Java架構

分享網際網路最新文章 關注網際網路最新發展