1. 程式人生 > >java類的繼承的一些細節

java類的繼承的一些細節

翻譯 關鍵字 當前 fault args 消失 構造 子類 繼續

  類的繼承是java面向對象體系的一個重要方面(封裝、繼承、多態),對於java類的繼承,需要註意如下細節。

  1.構造函數。

  如果一個類沒有任何構造函數,系統會默認分配一個無參的構造函數給它,這個構造函數什麽都不做。但是一旦類中有定義其他有參數的構造函數,且沒有顯式的的定義無參構造函數,那麽系統不會為該類提供一個默認的無參構造函數。

  而類的繼承,子類繼承了 父類的所有可繼承的成員和方法,那什麽是不可繼承的呢?答案就是構造函數。構造函數也是一個函數,但是子類卻不可以繼承。雖然子類無法繼承父類構造函數,但是他卻必須調用父類的構造函數。通過super(參數列表)來調用父類的構造函數。當子類沒有顯式調用父類構造函數時,並且沒有調用其他構造函數,程序默認為其調用父類無參構造函數。

  註意以下幾種情況

  a.父類和子類都沒有顯式的定義任何構造函數

  

class Father{
    private String userName;
    private int age;
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getAge() {
        return age;
    }
    
public void setAge(int age) { this.age = age; } } class son extends Father{ }

  如上代碼所示,父類與子類都沒有顯式定義一個構造函數。但是系統默認為二者均提供一個無參構造函數。又因為子類必須調用父類的構造函數,而當程序沒有顯式的調用父類構造函數的時候,系統默認為其調用父類無參構造函數。所以上述代碼效果其實與下面代碼是一樣的。

class Father{
    private String userName;
    private int age;
    public
String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //顯式無參構造函數 public Father(){ } } class son extends Father{ public son(){ super(); } }

  b.父類包含有參構造函數,但無顯式定義無參構造函數,子類也並未顯式調用父類構造函數,此時將出現錯誤。

class Father{
    private String userName;
    private int age;
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    //顯式無參構造函數
    public Father(String userName,int age){
        this.userName=userName;
        this.age=age;
    }

}
class son  extends Father{
}

ide此時會在son的定義處給出提示,Implicit super constructor Father() is undefined for default constructor. Must define an explicit constructor。翻譯一下就是父類沒有顯式的定義一個無參構造函數,而我子類也沒有顯式調用父類構造函數,所以程序默認的去調用一個不存在的父類的無參構造函數。當然出錯了。

  只要顯式的調用父類的一個構造函數,既可以解除錯誤。為son加入下面構造函數。

public son(String userName,int age){
        super(userName,age);
    }

  或者更簡單點,給父類顯式添加一個無參構造函數,這樣子類即便不顯式調用父類構造函數,也可以避免上述錯誤。事實上,一般情況下,當我們定義了有參構造函數,最好是顯式定義一個無參構造函數,以避免此類錯誤的出現。

  2.子類實例化的過程

  子類的實例化過程是較為復雜的一個過程,必須弄清楚,因為很多看起來很含糊的問題都是因為沒有弄清楚這個問題而導致的。

  例如son是father的子類,father是grandfather的子類,則son的實例化將經過如下過程。

  首先,我們整理出一個流程。簡稱大循環.

  第一步:分配成員變量的存儲空間,並且按java的規則進行默認初始化(如int型的值為0,引用型為null)。此時其實該子類(son)已經在內存中占有了一席之地。

  第二步:給構造函數的參數賦值,比如說我們執行的是son s=new ("tom",18),則將“tom”和18分別賦值給構造函數的參數userName和age,註意這一步還沒有執行到構造函數裏面的具體代碼。

  第三步:調用父類構造函數。

  第四步:執行son 類中成員變量的顯式初始化,就是我們在定義成員變量的時候,有時候會顯式初始化。比如private String userName="xdx"。這就是顯式的初始化。

  第五步:執行子類構造函數的代碼。

  上述的流程,是沒有考慮父類的情況,但是通過第1點關於構造函數的討論,我們知道,在執行子類構造函數的時候,必須先顯式或隱式的調用父類構造函數。我們將這個過程在細做分解,則形成一個小循環。將這個小循環插入(替代)上面大循環的第三步。

  第(1)步:father類執行類似大循環中第二步,即給father類構造函數中的形式參數賦值。

  第(2)步:調用father類的父類的構造函數

  第(3)步:執行father類中的成員變量的顯示初始化。

  第(4)步:執行father類的構造函數

  由於父類又可能有父類,所以在小循環中的第二步起始處繼續插入一個小循環,一直到追溯到object類為止,因為object類就是所有類的父類。

  以上便是子類實例化的詳細過程。有幾個地方需要註意。

  a.整個過程中在內存中只實例化出了一個對象,便是子類這個對象,父類並沒有被實例化。

  b.註意執行成員變量顯式初始化這一步,它針對的是當前類,並且是顯式的。也就是說當前處於子類的循環中,執行的是子類的成員變量顯式初始化,如果是處於父類的循環中,執行的是父類的成員變量的顯式初始化。雖然子類繼承了父類的成員變量,但這個初始化過程並不會重復。

  舉個例子。

  

class Father{
    private String userName="tom";
    private int age;
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Father(String userName,int age){
        this.userName=userName;
        this.age=age;
    }

}
class son  extends Father{
    private String university="xmu";
    
    public String getUniversity() {
        return university;
    }

    public void setUniversity(String university) {
        this.university = university;
    }

    public son(String userName,int age,String university){
        super(userName,age);
        this.university=university;
    }
}

  上述father類有兩個成員變量,userName和age,son繼承了他們並且自己有一個新的成員university。

  執行如下代碼。

public class ExtendDemo {

    public static void main(String[] args) {
        son s=new son("xdx",18,"Peking University");
        System.out.println(s.getUserName());
        System.out.println(s.getUniversity());
    }

  上述代碼的son類實例化過程為(假設father類就是最頂級的類了,不考慮object類):

  (1).分配存儲空間,並默認初始化,即userName=null;age=0;university=null;(此時成員變量userName=null,age=0;university=null)

  (2).給構造函數的參數賦值,userName(參)="xdx",age(參)=18,university(參)="Peking University"。標註參是為了與成員變量混淆。(此時成員變量userName=null,age=0;university=null)

  (3)調用父類構造函數public Father(String userName,int age),這一步分為:a.先給構造函數的參數賦值,userName="xdx",age=18,(此時成員變量userName=null,age=0;university=null);b.顯式執行成員變量初始化,即userName="tom"。(此時成員變量userName=“tom”age=0;university=null)c.然後執行構造函數的代碼:即this.userName=userName; this.age=age;這兩句代碼。(此時成員變量userName="xdx",age=18;university=null)

  (4)給son類的成員進行顯式初始化,即 private String university="xmu";(此時成員變量userName="xdx",age=18;university="xmu").

  (5)執行son類的構造函數,即 this.university=university;這句,(此時成員變量userName="xdx",age=18;university="Peking University"

  執行結果是:

  xdx
  Peking University

  我們再改變程序,驗證一下結果。

  

public class ExtendDemo {

    public static void main(String[] args) {
        son s=new son("xdx",18,"Peking University");
        System.out.println(s.getUserName());
        System.out.println(s.getAge());
        System.out.println(s.getUniversity());
    }

}
class Father{
    private String userName="tom";
    private int age=10;
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    //顯式無參構造函數
    public Father(){
        
    }
    public Father(String userName,int age){
        this.userName=userName;
        this.age=age;
    }

}
class son  extends Father{
    private String university="xmu";
    
    public String getUniversity() {
        return university;
    }

    public void setUniversity(String university) {
        this.university = university;
    }

    public son(String userName,int age,String university){
        this.university=university;
    }
}

  執行結果

  tom
  10
  Peking University

  具體的原因就不分析了,只要按照上述的步驟來分析,很容易就能得到正確的結果。

  3.方法的重寫,同名成員變量的隱藏。

  子類繼承父類的方法,方法名、參數、返回值都必須是一樣的,需要註意的是,如果你重寫了方法(就是你顯式地再定義了一個跟父類一樣的方法),在該重寫的方法中還要調用父類方法中的代碼邏輯,必須要用super關鍵字去調用,程序並不會隱式地去調用

  方法的重寫比較好理解,說簡單點就是覆蓋了,關鍵是成員變量,有這樣一種情況,假如我在子類中定義了一個跟父類同樣名稱的成員變量。這是什麽情況呢?

  註意,在java中是沒有所謂的覆蓋父類成員變量的說法的。一旦在子類中定義了一個與父類同名的成員變量,父類的成員變量將隱藏。所謂的隱藏就是對子類不可見,子類也不會操縱父類的這個成員變量了。子類只能操縱(訪問或者改變)自己這個成員變量,如若他要訪問或者父類的成員變量,則必須使用super關鍵字。

  看如下例子。

  

public class ExtendDemo {

    public static void main(String[] args) {
        son s=new son();
        System.out.println(s.userName);
        System.out.println(s.getUserName());
    }

}
class Father{
    private String userName="fatherName";
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
}
class son  extends Father{
    public String userName="sonName";
}

  執行結果是:

  sonName
  fatherName

  由上述執行結果我們可以了解到,s.userName獲取到的是子類的userName,而getUserName因為是繼承自父類的方法,其得到的是父類的userName。父類的userName並沒有消失(被覆蓋),它只是隱藏了。

  這樣理解還是有點不清晰,可以換種方法說,當子類沒有定義與父類同名的成員變量時,子類是擁有這個成員變量的,當子類定義了一個與父類同名的成員變量,先前這個變量被隱藏了。你不能直接再使用son.userName這種方式去獲取它,必須通過父類的方法(super)去獲取它。

  

public class ExtendDemo {

    public static void main(String[] args) {
        son s=new son();
        System.out.println(s.userName);
        System.out.println(s.getUserName());
        System.out.println(s.getFatherName());
    }

}
class Father{
    private String userName="fatherName";
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
}
class son  extends Father{
    public String userName="sonName";
    public String getFatherName(){
        return super.getUserName();
    }
}

  執行結果:

  sonName
  fatherName
  fatherName

  在getFatherName中,我們使用了super.getUserName。其實他跟s.getUserName()的效果是一模一樣的,都是通過父類來獲取userName這個被隱藏了屬性。

  那假如我重寫了getUserName這個方法呢?看如下代碼。

  

public class ExtendDemo {

    public static void main(String[] args) {
        son s=new son();
        System.out.println(s.userName);
        System.out.println(s.getUserName());
        System.out.println(s.getFatherName());
    }

}
class Father{
    private String userName="fatherName";
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
}
class son  extends Father{
    public String userName="sonName";
    
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getFatherName(){
        return super.getUserName();
    }
}

  執行結果是:

  sonName
  sonName
  fatherName

  由於我現在重寫了getUserName方法,此時它所操縱的userName自然是子類的userName了。

  所以一般情況下,當我們再父類中已經定義了一個成員變量,在子類中不會去定義一個相同名字的成員變量的。因為當你想刻意的訪問子類這個成員變量,但是卻沒有重寫父類的get方法時,是訪問不到這個成員變量的。而是會訪問到父類的那個成員變量。而有時候你還渾然不覺。

  

java類的繼承的一些細節