問題引出
在子類中可以通過使用super關鍵字來調用父類的被重寫的方法,與構造方法中調用父類的構造方法不同,此處調用語句放在哪裡是由程式邏輯決定的,這是因為在構造方法中要首先建立父類的對象,是以需要将顯式調用父類構造方法的語句放于子類構造方法的第一行(預設系統會添加對父類無參構造函數的調用),而在子類的非構造方法中調用父類的被重寫方法時,父類的對象已經建立了,就沒有像調用父類構造方法那樣的限制了。其實這兩種super調用的意義是沒有任何關系的。
this所指的是目前調用方法的對象,而super所指的則是目前調用方法的對象的直接父類的對象。
當在子類的構造方法中使用super關鍵字調用父類的構造方法時,對于被重寫屬性的操作是針對父類屬性的,對于子類重寫的屬性沒有影響。
下面給出一個程式,運作這個程式就能體會上文所描述的狀況。最後主要說明一下這個程式中的對象執行個體化過程。
public class TestInstanceObject
{
public static void main(String[] args)
{
Apple a = new Apple(, );
System.out.println("蘋果的重量是" + a.weight + "蘋果的單價是" + a.price);
System.out.println("子類中的weight屬性值為" + a.getWeight());
System.out.println("父類中的weight屬性值為" + a.getSuperWeight());
}
}
class Fruit
{
int weight;
int price;
// constructors overload
public Fruit()
{
System.out.println("這是Fruit類的無參構造方法");
}
public Fruit(int weight, int price)
{
this.weight = weight;
this.price = price;
System.out.println("我是Fruit類具有兩個整型參數的構造方法,我完成對從我這裡繼承的屬性的初始化,即使weight屬性被隐藏了");
}
}
class Apple extends Fruit
{
int weight = ;
//constructors overload
public Apple()
{
System.out.println("我是Apple類的無參構造方法,我會隐式調用父類的無參構造方法來完成相應于此處所産生的子類對象的父類對象");
}
public Apple(int weight, int price)
{
super(weight, price); //call the parent constructormethod
System.out.println("我是Apple類具有兩個整型參數的構造方法,我顯式調用了父類的具有兩個整型參數的構造方法");
}
public int getWeight()
{
return weight;
}
public int getSuperWeight()
{
return super.weight;
}
}
背景知識
背景知識一 類初始化時機
第一:生成該類對象的時候,會初始化該類及該類的所有父類;
第二:通路該類的靜态成員的時候(子類中通路父類中定義的靜态變量時,隻會初始化父類,不會造成子類的初始化);
第三:
class.forName("類名");
背景知識二 JVM類加載機制
加載、連結與初始化
加載:由類的加載器執行,将類的.class檔案載入記憶體,并根據位元組碼建立Class對象;
連結:驗證該類中的位元組碼,為靜态變量配置設定記憶體并賦以預設初始值,如果必要,會解析該類建立的對其他類的引用;(驗證<可優化>、準備<配置設定記憶體>、解析<不一定>)
初始化:初始化是類加載的最後一步,其本質上是執行類構造方法,而類構造方法則是由編譯器按照類變量指派語句、靜态語句塊的出現順序所收集生成的(是以不一定每個類都有類構造方法)。并且,JVM保證在子類的類構造方法執行前,完成對父類類構造方法的執行,也就是對超類進行初始化(自然包括對超類進行加載、連結),包括為靜态函數配置設定入口位址。
初始化被放到了初次使用類的靜态方法(構造器隐式地是靜态的)或非常數靜态域時才執行。
完成這三步後,就能夠去建立這個類的對象了,也就是去對執行個體變量進行初始化,執行對執行個體變量進行操作的語句或語句塊,然後是執行執行個體構造器。
其實對于使用new關鍵字隐式調用構造方法完成對象的建立這一過程而言,構造方法并非獨自完成了建立對象的全部任務。實際上,當程式員調用構造函數時,系統首先會為該對象配置設定記憶體空間,并為這個對象執行預設初始化,這之後對象已經存在了。也就是說在真正執行構造方法之前對象就已經産生了,隻是這個對象還不能為外部程式所通路,隻能在構造函數中通過this關鍵字引用。
當構造方法的執行體執行結束後,這個對象作為構造器的傳回值被傳回,通常還會賦給一個引用類型的變量,進而讓外部程式可以通路該對象。是以當建立一個對象時,如:
p隻不過是一個引用類型的變量而已,并不是對象,真正的對象是
new People();
所建立的我們看不見、摸不着的,在記憶體中存儲着的實體。
歸納總結一下,執行個體化一個對象的過程是:
1.為對象配置設定記憶體,執行預設初始化;
2.執行顯式初始化,即類成員變量聲明時的指派語句與執行個體初始化塊。
兩者順序執行,并且初始化塊中可以對在其之後聲明的屬性進行操作(但不能引用)。即:
class Car
{
{
brand= "Ford";
}
String brand;
}
3.執行構造方法;
4.傳回對象在記憶體中的首位址等重要資訊。
子類對象建立過程
好了,接下來就對上面程式中的
Apple a = new Apple(50, 2);
語句執行過程進行分析。
首先是加載類階段,鍵入java指令後開始執行程式(首先會加載main方法所在的類-主類,本示例代碼主類中沒有其餘變量與操作,故而不進行分析),執行到語句
Apple a = new Apple(50, 2);
時,系統發現類Apple還沒有加載,于是JVM就去加載該類,當執行完加載、連結,執行初始化時,JVM又發現Apple類的直接父類Fruit類也未加載,于是又去加載Fruit類,同樣,JVM對Fruit類完成加載、連結,進行初始化時又發現Fruit類的父類Object類也未加載,于是又去加載Object類(JVM加載類的前兩步,加載是去加載class檔案,連結是對static成員進行預設初始化,第三步初始化是去順序執行對靜态變量的簡單指派語句以及靜态初始化語句塊),加載完畢Object類後,傳回對Fruit類完成初始化,然後再對Apple類進行初始化,就完成了對所有涉及到的類的加載,整個過程是遞歸進行的。随後就進入建立對象階段,沒有父親就沒有孩子,是以會去自上而下建立父類的對象,其實這是因為在子類的構造器中至少會隐式調用父類的預設構造器。實際上并沒有真正如同new一般建立直接或間接父類的對象,因為子類會繼承父類的屬性,是以對父類對象的建立最主要的目的就是對子類所繼承的屬性的初始化。根據我的了解,首先是為屬性(所有的屬性,包括繼承而得的,即使是被隐藏的)配置設定記憶體空間并賦以預設初始值,然後從上到下遞歸進行兩個操作:執行簡單指派語句與初始化塊,執行構造方法。也就是說,這一系列步驟之後,由上至下所有的屬性都會配置設定記憶體與初始化,即使存在隐藏覆寫的情形。至于在構造方法重載的情況下調用哪一個構造方法,又需要根據直接子類的構造方法調用而定。我們知道,當子類的構造方法不使用super關鍵字顯式調用父類的構造方法時,編譯時會在子類的構造方法中自動加上一條
super();
語句來實作對父類無參構造方法的調用,是以在調用父類的構造方法時還需要根據直接子類所調用父類構造方法的情況來進行調用,依次類推。
對于上述程式,new關鍵字之後是對于Apple類的帶有兩個整型參數的構造函數的隐式調用-new調用構造方法就叫隐式調用,是以就會去Apple類中查找執行帶有兩個整型參數的構造方法,即
public Apple(int weight, int price);
,在該構造方法又中顯式調用了父類中有兩個整型參數的構造方法,是以就會到父類中去查找并執行帶有兩個整型參數的構造方法,即
public Fruit(int weight, int price);
,在該構造方法中,通過this關鍵字完成了對目前父類對象屬性的初始化,當然這兩個屬性被Apple類繼承了,也就是屬于Apple類的屬性,就好像他們是定義在Apple類中的。
以上所描述的隻是如何選擇所調用的構造方法,實際上由上到下每一個類中執行簡單指派語句以及初始化塊與調用構造方法之間是按順序遞歸進行的,一個類完成之後其直接子類再去完成,直至到進行執行個體化的子類。在本程式中,Apple類定義了一個weight屬性,由于這個屬性與從父類中繼承而來的屬性重名,是以父類中的屬性被隐藏,在子類中是看不到的,除非使用super關鍵字進行調用。自此Apple類的對象a就建立完成了。在主函數中分别調用Apple類中getWeight與getSuperWeigth方法,顯示出子類中定義的weight屬性值與所隐藏的父類中的weight屬性值。運作結果如下: