1. 程式人生 > >Java重寫(覆蓋)與隱藏

Java重寫(覆蓋)與隱藏

java中方法重寫(override)的要求:
1、子類中的方法與父類中的方法的返回型別:

如果返回型別是引用型別,則重寫方法的返回型別可以宣告為超類方法宣告的返回型別的子型別;如果返回型別是基本型別,則重寫方法的返回型別必須和超類方法的返回型別相同。  
2、子類中的方法與父類中的方法有相同的方法名稱

3、子類中的方法與父類中的方法有相同的引數列表 
4、子類中的方法的訪問級別不能低於父類中該方法的訪問級別(舉例:
   父類方法的級別是protected,那麼子類重寫該方法,其級別必須是protected或者public,一定和父類的訪問級別相同或者更寬,否則編譯無法通過) (public>protected>default>private)  
5、子類中方法丟擲的異常範圍不能大於父類中方法丟擲的異常的範圍,
      (子類可以不丟擲異常也滿足該原則,即是說父類丟擲異常,子類不丟擲異常,編譯通過沒問題。)

例1.class Parent {

      public String publicField = "父類變數";

       public void r(){
        System.out.println("父類函式");
      }
  }

 class Child extends Parent {

     public String publicField = "子類變數";
     public void r(){
        System.out.println("子類函式");
     }

 }
 public static void main(String[] args) {
     Parent subClass = new StringTest().new Child();
     System.out.println(subClass.publicField);
     subClass.r();

 }

//輸出:父類變數   子類函式

分析:隱藏欄位 :欄位不可以被覆蓋而只能被隱藏。 

例2 class A{
    private int i;
      A(){
         i=1;
         f();
      } 
    public void f(){
      System.out.println("i="+i);
    }
 }
 class B extends A{
      private int j;
     B(){
          j=1;
      }
     public void f(){
       super.f();
       System.out.println("j="+j);
    }
  }
 public static void main(String[] args){
  StringTest st=new StringTest();
  StringTest.A a=st.new B();
  a.f();//輸出i=1   j=0   i=1  j=1

 }

分析:多型new的物件,都是先去到父類中,方法則是在父類中找到最匹配的方法,再看有沒被子類覆蓋,如有,執行子類方法;如無,執行父類方法,即使子類中存在引數型別最匹配的方法也不會被執行

例3.class Father
{
   int b=6;
  void print()
  {
    System.out.println("Father'b="+b);
  }

}
class Son extends Father
{
   int b=60;
   void print()
  {
    System.out.println("Son'b="+b);
  }
}

Father son=new Son();
System.out.println("b="+son.b);
--結果:b=6.
son.print();
--結果Son'b=60

 分析:隱藏欄位 :欄位不可以被覆蓋而只能被隱藏。   
    

例4.class Father
{
  static void print()
  {
    System.out.println("Father");
  }

}
class Son extends Father
{
   static void print()
  {
    System.out.println("Son");
  }
}

Father f1 = new Fater();
Father f2 = new Son();

f1.print()  //  father
f2.print()  //  father

分析:  隱藏靜態成員 :類中的靜態成員(無論是欄位還是方法)不可以被覆蓋,只能被隱藏。  


例4.class A {
         public String show(D obj){
                return ("A and D");
         }
         public String show(A obj){
                return ("A and A");
         }
}
class B extends A...{
         public String show(B obj){
                return ("B and B");
         }
         public String show(A obj){
                return ("B and A");
         }
}
class C extends B...{}
class D extends B...{}

問題:以下輸出結果是什麼?

        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();
        System.out.println(a1.show(b));   ①
        System.out.println(a1.show(c));   ②
        System.out.println(a1.show(d));   ③
        System.out.println(a2.show(b));   ④
        System.out.println(a2.show(c));   ⑤
        System.out.println(a2.show(d));   ⑥
        System.out.println(b.show(b));     ⑦
        System.out.println(b.show(c));     ⑧
        System.out.println(b.show(d));     ⑨
   

答案   ①   A and A
              ②   A and A
              ③   A and D
              ④   B and A
              ⑤   B and A
              ⑥   A and D
              ⑦   B and B
              ⑧   B and B
              ⑨   A and D


分析:

        ①②③比較好理解,一般不會出錯。④⑤就有點糊塗了,為什麼輸出的不是"B and B”呢?!!先來回顧一下多型性。

        執行時多型性是面向物件程式設計程式碼重用的一個最強大機制,動態性的概念也可以被說成“一個介面,多個方法”。Java實現執行時多型性的基礎是動態方法排程,它是一種在執行時而不是在編譯期呼叫過載方法的機制。

        方法的重寫Overriding和過載Overloading是Java多型性的不同表現。重寫Overriding是父類與子類之間多型性的一種表現,過載Overloading是一個類中多型性的一種表現。如果在子類中定義某方法與其父類有相同的名稱和引數,我們說該方法被重寫(Overriding)。子類的物件使用這個方法時,將呼叫子類中的定義,對它而言,父類中的定義如同被“遮蔽”了。如果在一個類中定義了多個同名的方法,它們或有不同的引數個數或有不同的引數型別,則稱為方法的過載(Overloading)。Overloaded的方法是可以改變返回值的型別。

        當超類物件引用變數引用子類物件時,被引用物件的型別而不是引用變數的型別決定了呼叫誰的成員方法,但是這個被呼叫的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。 (但是如果強制把超類轉換成子類的話,就可以呼叫子類中新新增而超類沒有的方法了。)

        好了,先溫習到這裡,言歸正傳!實際上這裡涉及方法呼叫的優先問題 ,優先順序由高到低依次為:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。讓我們來看看它是怎麼工作的。

        比如④,a2.show(b),a2是一個引用變數,型別為A,則this為a2,b是B的一個例項,於是它到類A裡面找show(B obj)方法,沒有找到,於是到A的super(超類)找,而A沒有超類,因此轉到第三優先順序this.show((super)O),this仍然是a2,這裡O為B,(super)O即(super)B即A,因此它到類A裡面找show(A obj)的方法,類A有這個方法,但是由於a2引用的是類B的一個物件,B覆蓋了A的show(A obj)方法,因此最終鎖定到類B的show(A obj),輸出為"B and A”。

        再比如⑧,b.show(c),b是一個引用變數,型別為B,則this為b,c是C的一個例項,於是它到類B找show(C obj)方法,沒有找到,轉而到B的超類A裡面找,A裡面也沒有,因此也轉到第三優先順序this.show((super)O),this為b,O為C,(super)O即(super)C即B,因此它到B裡面找show(B obj)方法,找到了,由於b引用的是類B的一個物件,因此直接鎖定到類B的show(B obj),輸出為"B and B”。

        按照上面的方法,可以正確得到其他的結果。

        問題還要繼續,現在我們再來看上面的分析過程是怎麼體現出藍色字型那句話的內涵的。它說:當超類物件引用變數引用子類物件時,被引用物件的型別而不是引用變數的型別決定了呼叫誰的成員方法,但是這個被呼叫的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。還是拿a2.show(b)來說吧。


        a2是一個引用變數,型別為A,它引用的是B的一個物件,因此這句話的意思是由B來決定呼叫的是哪個方法。因此應該呼叫B的show(B obj)從而輸出"B and B”才對。但是為什麼跟前面的分析得到的結果不相符呢?!問題在於我們不要忽略了藍色字型的後半部分,那裡特別指明:這個被呼叫的方法必須是在超類中定義過的,也就是被子類覆蓋的方法。B裡面的show(B obj)在超類A中有定義嗎?沒有!那就更談不上被覆蓋了。實際上這句話隱藏了一條資訊:它仍然是按照方法呼叫的優先順序來確定的。它在類A中找到了show(A obj),如果子類B沒有覆蓋show(A obj)方法,那麼它就呼叫A的show(A obj)(由於B繼承A,雖然沒有覆蓋這個方法,但從超類A那裡繼承了這個方法,從某種意義上說,還是由B確定呼叫的方法,只是方法是在A中實現而已);現在子類B覆蓋了show(A obj),因此它最終鎖定到B的show(A obj)。這就是那句話的意義所在。