構造器是一個特殊的方法,這個特殊方法用於建立執行個體時執行初始化。 構造器是建立對象的重要途徑(即使使用原廠模式、反射等方式建立對象,其實質還是依賴於構造器),因此,Java類必須包含一個或一個以上的構造器。
一、使用構造器執行初始化
構造器最大的用處就是在建立對象時執行初始化。建立一個對象時,系統為這個對象的執行個體變數進行預設初始化,這個預設的初始化把所有基本類型的執行個體變數設為0(對數值型執行個體變數)或false(對布爾型執行個體變數),把所有參考型別的執行個體變數設為null。
如果向改變這種預設的初始化,想讓系統建立對象時就為該對象的執行個體變數顯示指定初始化,就可以通過構造器來實現。
註:如果程式員沒有為Java類提供任何構造器,則系統會為這個類提供一個無參數的構造器,這個構造器執行體為空白,不做任何事情。
下面看一個例子:
public class ConstructorTest{ public String name; public int count; //提供自訂的構造器,該構造器包含兩個參數 public ConstructorTest(String name , int count) { //構造器this代表他進行初始化的對象 this.name = name; this.count = count; } public static void main(String[] args) { //使用自訂的構造器來建立對象 //系統將會對該對象執行自訂的初始化 ConstructorTest tc = new ConstructorTest("Java",1); //輸出ConstructorTest對象的name和count兩個執行個體變數 System.out.println(tc.name); System.out.println(tc,count); }}
運行上面程式,可以看到,輸出ConstructorTest對象時,它的name執行個體變數是Java,count執行個體變數時0。
問:構造器是建立對象的途徑,是不是說構造器完全負責建立Java對象。
答:不是。構造器是建立Java對象的重要途徑,通過new關鍵字調用構造器時,構造器也返回了該類的對象,但這個對象不是完全由構造器負責建立的。 實際上,當程式在調用構造器時,系統會先為該對象分配記憶體空間,並為這個對象執行預設初始化,這個對象已經產生了 —— 這些操作在構造器執行子安就都完成了。也就是說,當系統開始執行構造器的執行體之前,系統已經建立了一個對象,只是這個對象還不能被外部系統訪問。只能在該構造器中通過this來引用。當構造器的執行體執行結束後,這個對象作為構造器的傳回值被返回,通常還會賦給另一個參考型別的變數,從而讓外部程式可以訪問該變數。
一旦程式員提供了自訂的構造器,系統就不再提供預設的構造器。因此上面的ConstructorTest類不能再通過new ConstructorTest();代碼來建立執行個體,因為該類不再包含無參數的構造器。
如果希望保留無參數的構造器,或者希望有多個初始化過程,則可以為該類提供多個構造器。如果一個類中提供了多個構造器,就形成了構造器的重載。
因為構造器主要用於被其他方法調用,用以返回該類的執行個體,因而把構造器設定成public存取權限,從而允許系統中任何位置的類來建立該類的對象。除非在一些極端的情況下,業務需要限制建立該類的對象,可以把構造器設定成其他存取權限,例如protected,主要用於被其子類調用。
二、構造器重載
同一個類裡具有多個構造器,多個構造器的形參列表不同,即成為構造器的重載。構造器重載允許Java類裡包含多個初始化邏輯,從而允許使用不同的構造器來初始化Java對象。
構造器重載和方法重載基本相似:要求構造器的名字相同。這一點無須特別要求,因為構造器必須與類名相同,所以同一個類的所有構造器名看到相同。為了讓系統區分不同的構造器,多個構造器的參數列表必須不同。
下面的Java類示範了構造器重載,利用構造器重載就可以通過不同的構造器來建立Java對象。
public class ConstructorOverload{ public String name; public int count; //提供無參數的構造器 public ConstructorOverload() {} //提供帶兩個參數的構造器 //對該構造器返回的對象執行初始化 public ConstructorOverload(String name,int count) { this.name = name; this.count = count; } public static void main(String[] args) { //通過無參數構造器建立ConstructorOverload對象 ConstructorOverload ocl = new ConstructorOverload(); //通過有參數構造器建立ConstructorOverload對象 ConstructorOverload oc2 = new ConstructorOverload("java" , 1); System.out.println(oc1.name + " " + oc1.count); System.out.println(oc2.name + " " + oc2.count); }}
上面ConstructOverload類提供了兩個重載的構造器,兩個構造器的名字相同,但形參列表不同,系統通過new調用構造器時,系統將根據傳入的實參列表來決定調用哪個構造器。
如果系統中包含了多個構造器,其中一個構造器的執行體裡完全包含了另一個構造器的執行體。
構造器A
程式碼一程式碼二程式碼三
構造器B
程式碼一程式碼二程式碼三程式碼四
可以看出,構造器B完全包含了構造器A。對於這種完全包含的情況,如果是兩個方法之間存在這種關係,則可在方法B中調用方法A。但構造器不能直接被調用,調用構造器必須用new 關鍵字來調用構造器,這會導致系統重新建立一個對象。為了在構造器B中調用構造器A中的初始化代碼,又不會重新建立一個Java對象,可以使用this關鍵字來調用相應的構造器。
下面代碼實現了在一個構造器中直接使用另一個構造器的初始化代碼
public class Apple{ public String name; public String color; public double weight; public Apple() {} //兩個參數的構造器 public Apple(String name,String color) { this.name = name; this.color = color; } //三個參數的構造器 public Apple(String name,String color ,double weight) { //通過this調用另一個重載的構造器的初始化代碼 this(name , color); //下面this引用該構造器正在初始化的Java對象 this.weight = weight; }}
上面的Apple類包含了三個構造器,其中第三個構造器通過this來調用另一個重載構造器的初始化代碼。程式中this(name,color);調用表明調用該類另一個帶兩個字串參數的構造器。
使用this調用另一個重載構造器只能在構造器中使用,而且必須作為構造器執行體的第一條語句。使用this調用重載構造器時,系統會根據this後括弧裡的實參調用形參列表與之對應的構造器。
為什麼要用this來調用另一個重載的構造器。我把另一個構造器裡的代碼複製、粘貼到這個構造器裡不就可以了嗎。
答:如果僅僅從軟體功能的角度上來看,這樣複製、粘貼確實可以實現這個效果;但從軟體工程的角度來看,這樣做事相當糟糕的。在軟體開發有一個規則,不要把相同的代碼書寫兩次以上。因為軟體是一個需要不斷更新的產品,如果有一天需要更新上面構造器A中的初始化代碼,假設構造器B、構造器C……裡都包含了相同的初始化塊代碼,則需要同時開啟構造器A、構造器B、構造器C…..的代碼進行修改;反之,如果構造器B、構造器C……是通過this調用了構造器A的初始化代碼,則只需要開啟構造器A進行修改即可,盡量避免相同的代碼重複出現,充分利用每一段代碼,既可以讓程式更加簡潔,也可以降低軟體的維護成本。