天天看點

JavaSE(二)之繼承、封裝、多态一、封裝(資料的隐藏)二、方法的重載三、繼承四、super關鍵字五、方法重寫(方法覆寫)六、多态七、instanceof和類型轉換

學習完類與對象終于認識到什麼是類,什麼是對象了。接下來要看的就是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類型的子類型,如果不是則運作就會報錯。