構造函數在創建對象時用來初始化對象。 它與類具有相同的名稱,並且在語法上與方法類似。但是,構造函數沒有返回值類型。
通常,使用構造函數為類定義的實例變數提供初始值,或執行創建完全形成的對象所需的其他啟動過程。
所有類都有構造函數,無論是否定義了構造函數,因為Java會自動提供一個默認構造函數,將所有成員變數初始化為零。 但是,一旦定義了自己的構造函數,就不再使用默認構造函數。
語法
以下是構造函數的語法 -
class ClassName {
ClassName() {
}
}
每當使用new
關鍵字創建類的實例時,都會調用構造函數並返回類的對象。 由於構造函數只能將對象返回給類,它是由java運行時隱式完成的,不應該向它添加返回類型。
如果將返回類型添加到構造函數,那麼它將成為類的方法。 這是java運行時區分普通方法和構造函數的方式。 假設在Employee
類中有以下代碼。
import java.io.*;
public class Employee {
public Employee() {
System.out.println("Employee構造函數");
}
public Employee Employee() {
System.out.println("Employee一般方法");
return new Employee();
}
}
這裏第一個是構造函數,因為它沒有返回類型和返回語句。 第二個是一個常規方法,我們再次調用第一個構造函數來獲取Employee
實例並返回它。建議不要將方法名稱與類名相同,因為會容易造成混淆。
1. Java中構造函數的類型
java中有三種類型的構造函數。
- 默認構造函數
- 無參數構造函數
- 參數化構造函數
下麵將通過示例程式來學習這些構造函數類型。
1.1. Java中的默認構造函數
不需要始終在類代碼中提供構造函數實現。如果不提供構造函數,那麼java提供默認的構造函數實現供我們使用。 下麵來看一個使用默認構造函數的簡單程式,它沒有顯式定義構造函數。
package com.zaixian.constructor;
public class Car {
public static void main(String[] args) {
Car c = new Car();
}
}
- 默認構造函數的唯一作用是初始化對象並將其返回給調用代碼。
- 默認構造函數始終沒有參數,只有在沒有定義現有構造函數的情況下由java編譯器提供。
- 大多數情況下,可以使用默認構造函數本身,因為可以通過
getter
/setter
方法訪問和初始化其他屬性。
1.2. 無參構造函數
沒有任何參數的構造函數稱為無參構造函數。 這就像覆蓋默認構造函數並用於執行一些預初始化的東西,例如:檢查資源,網路連接,日誌記錄等。通過以下代碼流覽一下java中的無參構造函數。
package com.zaixian.constructor;
public class Car {
// 無參構造函數
public Car() {
System.out.println("No-Args Constructor");
}
public static void main(String[] args) {
Car d = new Car();
}
}
當調用new new()
時,將調用無參構造函數。執行上面代碼,程式在控制臺輸出結果如下:
No-Args Constructor
1.3. 參數化構造函數
帶參數的構造函數稱為參數化構造函數。下麵來看看java中參數化構造函數的例子。
package com.zaixian.constructor;
public class Language {
private String name;
public Language(String n) {
System.out.println("Parameterized Constructor");
this.name = n;
}
public String getName() {
return name;
}
public static void main(String[] args) {
Language lang = new Language("Java");
System.out.println(lang.getName());
}
}
執行上面代碼,程式在控制臺輸出結果如下:
Parameterized Constructor
Java
2. 在Java中構造函數重載
當有多個構造函數時,它就是java中的構造函數重載。通過下麵示例代碼來瞭解java程式中構造函數重載。
package com.zaixian.constructor;
public class Data {
private String name;
private int id;
//no-args constructor
public Data() {
this.name = "Default Name";
}
//one parameter constructor
public Data(String n) {
this.name = n;
}
//two parameter constructor
public Data(String n, int i) {
this.name = n;
this.id = i;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
@Override
public String toString() {
return "ID="+id+", Name="+name;
}
public static void main(String[] args) {
Data d = new Data();
System.out.println(d);
d = new Data("Java");
System.out.println(d);
d = new Data("Maxsu", 25);
System.out.println(d);
}
}
3. Java中的私有構造函數
不能將abstract
,final
,static
和synchronized
關鍵字與構造函數一起使用。 但是,可以使用訪問修飾符來控制類對象的實例化。 使用public
訪問和default
訪問仍然沒有問題,但是將構造函數設為私有的用途是什麼? 在這種情況下,任何其他類都將無法創建該類的實例。
如果想要實現單例設計模式,構造函數是private
。 由於java自動提供默認構造函數,因此必須顯式創建構造函數並將其保持為private
。 客戶端類提供了實用程式靜態方法來獲取類的實例。 下麵給出了Data
類的私有構造函數的示例。
// private 構造函數
private Data() {
// 單例模式實現的空構造函數
// 可以在類的getInstance()方法中使用代碼
}
4. Java構造函數鏈接
當構造函數調用同一個類的另一個構造函數時,它被稱為構造函數鏈接。需要使用this
關鍵字來調用該類的另一個構造函數。有時它用於設置類變數的一些默認值。
請注意,另一個構造函數調用應該是代碼塊中的第一個語句。 此外,不應該有一個會產生無限迴圈的遞歸調用。下麵來看看java程式中構造函數鏈接的一個例子。
package com.zaixian.constructor;
public class Employee {
private int id;
private String name;
public Employee() {
this("Maxsu", 1999);
System.out.println("Default Employee Created");
}
public Employee(int i) {
this("Maxsu", i);
System.out.println("Employee Created with Default Name");
}
public Employee(String s, int i) {
this.id = i;
this.name = s;
System.out.println("Employee Created");
}
public static void main(String[] args) {
Employee emp = new Employee();
System.out.println(emp);
Employee emp1 = new Employee(10);
System.out.println(emp1);
Employee emp2 = new Employee("zaixian", 20);
System.out.println(emp2);
}
@Override
public String toString() {
return "ID = "+id+", Name = "+name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
上代碼代碼中,已經重寫了toString()
方法來列印一些有關Employee
對象的資訊。 以下是上面程式產生的輸出 -
Employee Created
Default Employee Created
ID = 1999, Name = Maxsu
Employee Created
Employee Created with Default Name
ID = 10, Name = Maxsu
Employee Created
ID = 20, Name = zaixian
從另一個構造函數調用一個構造函數,這個構造函數被稱為構造函數鏈接過程。
5. Java超類構造函數
有時一個類是從超類繼承的,在這種情況下,如果必須調用超類構造函數,那麼可以使用super
關鍵字。
請注意,super
造函數調用應該是子類構造函數中的第一個語句。 此外,在實例化子類構造函數時,java首先初始化超類,然後初始化子類。 因此,如果未顯式調用超類構造函數,則java運行時將調用default
或no-args
構造函數。下麵將通過一些示例程式來理解這些概念。
假設有兩個類,如下所示。
Person
類源代碼 -
package com.zaixian.constructor;
public class Person {
private int age;
public Person() {
System.out.println("Person Created");
}
public Person(int i) {
this.age = i;
System.out.println("Person Created with Age = " + i);
}
}
Student
類源代碼 -
package com.zaixian.constructor;
public class Student extends Person {
private String name;
public Student() {
System.out.println("Student Created");
}
public Student(int i, String n) {
super(i); // 超類構造函數調用
this.name = n;
System.out.println("Student Created with name = " + n);
}
}
現在,如果創建一個Student
對象,如下所示:
Student st = new Student();
那將輸出什麼結果? 上面代碼的輸出將是:
Person Created
Student Created
所以調用轉到了Student
類的no-args
構造函數,因為在第一個語句中沒有super
調用,所以調用了Person
類的no-args
或默認構造函數。如果使用Student
類的參數化構造函數作為 -
Student st = new Student(1999,"Maxsu");
那麼輸出將是:
Person Created with Age = 1999
Student Created with name = Maxsu
這裏輸出很明顯,因為我們顯式調用了超類構造函數,所以java不需要從no-args
構造函數這邊做任何額外的工作。
6. Java拷貝構造函數
Java拷貝構造函數將相同類的對象作為參數,並創建它的副本。有時需要另一個對象的副本來進行一些處理。 可以通過以下方式做到這一點:
- 實現克隆。
- 提供用於對象深拷貝的方法。
- 實現一個複製構造函數。
現在來看看如何編寫複製構造函數,假設有一個類:Fruits
,代碼如下。
package com.zaixian.constructor;
import java.util.ArrayList;
import java.util.List;
public class Fruits {
private List<String> fruitsList;
public List<String> getFruitsList() {
return fruitsList;
}
public void setFruitsList(List<String> fruitsList) {
this.fruitsList = fruitsList;
}
public Fruits(List<String> fl) {
this.fruitsList = fl;
}
public Fruits(Fruits fr) {
List<String> fl = new ArrayList<>();
for (String f : fr.getFruitsList()) {
fl.add(f);
}
this.fruitsList = fl;
}
}
請注意,Fruits(Fruits fr)
執行深拷貝以返回對象的副本。下麵通過一個測試程式,瞭解為什麼拷貝構造函數比拷貝對象更好。
package com.zaixian.constructor;
import java.util.ArrayList;
import java.util.List;
public class ConstructorTest {
public static void main(String[] args) {
List<String> fl = new ArrayList<>();
fl.add("Mango");
fl.add("Orange");
Fruits fr = new Fruits(fl);
System.out.println(fr.getFruitsList());
Fruits fr = fr;
fr.getFruitsList().add("Apple");
System.out.println(fr.getFruitsList());
fr = new Fruits(fr);
fr.getFruitsList().add("Banana");
System.out.println(fr.getFruitsList());
System.out.println(fr.getFruitsList());
}
}
執行上面查詢語句,得到以下結果:
[Mango, Orange]
[Mango, Orange, Apple]
[Mango, Orange, Apple]
[Mango, Orange, Apple, Banana]
請注意,當使用複製構造函數時,原始對象和它的副本彼此無關,並且其中一個中的任何修改都不會反映到其他對象中。