Java 中的構造函數

在Java中,构造函数用于创建类的实例。构造函数与方法几乎相似,只有两个不同之处——它的名称与类名相同,并且没有返回类型。有时构造函数也被称为特殊方法来初始化对象。

Java中的构造函数

每当我们使用new关键字创建一个类的实例时,构造函数被调用并返回类的对象。由于构造函数只能返回类的对象,Java运行时会隐式地执行这一操作,我们不需要为其添加返回类型。如果我们给构造函数添加返回类型,它将变成类的一个方法。这是Java运行时区分普通方法和构造函数的方式。假设我们在Employee类中有以下代码:

public Employee() {
	System.out.println("Employee Constructor");
}


public Employee Employee() {
	System.out.println("Employee Method");
	return new Employee();
}

这里的第一个是构造函数,请注意它没有返回类型和返回语句。第二个是一个普通方法,我们在其中再次调用第一个构造函数以获得Employee实例并返回它。建议不要将方法名与类名相同,因为这会造成混淆。

Java中的构造函数类型

Java中有三种类型的构造函数。

  1. 建構子
  2. 預設建構子
  3. 帶參數的建構子

讓我們透過範例程式來深入研究這些建構子類型。

Java 中的預設建構子

在類別程式碼中並非總是需要提供建構子實作。如果我們沒有提供建構子,Java 就會為我們提供預設建構子的實作。讓我們看一個簡單的範例程式,其中使用了預設建構子,因為我們不會明確定義建構子。

package com.journaldev.constructor;

public class Data {

	public static void main(String[] args) {
		Data d = new Data();
	}
}
  1. 預設建構子的唯一作用是初始化物件並將其返回給呼叫程式碼。
  2. 預設建構子總是不帶引數的,只有在沒有現有建構子的情況下,Java 編譯器才會提供它。
  3. 大多數情況下,我們滿足於使用預設建構子,因為其他屬性可以通過 getter 和 setter 方法進行訪問和初始化。

無參數建構子

package com.journaldev.constructor;

public class Data {
        無參數的構造函數稱為無參數構造函數。就像覆蓋默認構造函數一樣,用於進行一些預初始化工作,例如檢查資源、網絡連接、日誌記錄等。讓我們快速看一下Java中的無參數構造函數。
	public Data() {
		System.out.println("No-Args Constructor");
	}
	public static void main(String[] args) {
		Data d = new Data();
	}
}

//無參數構造函數

帶參數的構造函數

帶參數的構造函數被稱為參數化構造函數。讓我們看一下Java中帶參數的構造函數的例子。

package com.journaldev.constructor;

public class Data {

	private String name;

	public Data(String n) {
		System.out.println("Parameterized Constructor");
		this.name = n;
	}

	public String getName() {
		return name;
	}

	public static void main(String[] args) {
		Data d = new Data("Java");
		System.out.println(d.getName());
	}

}

Java中的構造函數重載

當我們有多於一個構造函數時,在Java中就是構造函數重載。讓我們看一下Java程序中構造函數重載的例子。

package com.journaldev.constructor;

public class Data {

	private String name;
	private int id;

	//無參數構造函數
	public Data() {
		this.name = "Default Name";
	}
	//帶一個參數的構造函數
	public Data(String n) {
		this.name = n;
	}
	//帶兩個參數的構造函數
	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("Pankaj", 25);
		System.out.println(d);
		
	}

}

Java 中的私有構造函數

請注意,我們不能使用 abstract、final、static 和 synchronized 關鍵字與構造函數。但是,我們可以使用訪問修飾符來控制類對象的實例化。仍然可以使用 publicdefault 訪問,但是將構造函數設置為私有有什麼用呢?在這種情況下,任何其他類都無法創建該類的實例。好吧,當我們想要實現單例設計模式時,就會將構造函數設置為私有。由於 Java 自動提供默認構造函數,我們必須明確地創建一個構造函數並將其設置為私有。客戶類提供了一個實用的靜態方法來獲取該類的實例。下面是 Data 類的私有構造函數示例。

// 私有構造函數
private Data() {
	// 用於單例模式實現的空構造函數
	// 可以在類的 getInstance() 方法內使用的代碼
}

Java 中的建構子鏈

當一個建構子調用同一類別的另一個建構子時,這被稱為建構子鏈。我們必須使用this 關鍵字來調用該類別的另一個建構子。有時它被用於設置類別變量的一些默認值。請注意,另一個建構子調用應該是程式區塊中的第一條語句。此外,不應該有導致無限迴圈的遞迴調用。讓我們看一個 Java 程式中的建構子鏈的例子。

package com.journaldev.constructor;

public class Employee {

	private int id;
	private String name;
	
	public Employee() {
		this("John Doe", 999);
		System.out.println("Default Employee Created");
	}
	
	public Employee(int i) {
		this("John Doe", 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("Pankaj", 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;
	}

}

I have overridden the toString() method to print some useful information about Employee object. Below is the output produced by above program.

Employee Created
Default Employee Created
ID = 999, Name = John Doe
Employee Created
Employee Created with Default Name
ID = 10, Name = John Doe
Employee Created
ID = 20, Name = Pankaj

注意一個建構子如何從另一個建構子中被調用,這就是建構子鏈的過程。

Java 超級建構子

有時候一個類別會繼承自一個超類,在這種情況下,如果我們需要調用超類的構造函數,我們可以使用super關鍵字。讓我們看一個使用超類構造函數的例子。請注意,超類構造函數的調用應該是子類構造函數中的第一個語句。同樣,當實例化子類構造函數時,Java首先初始化超類,然後才初始化子類。因此,如果沒有顯式調用超類構造函數,則Java運行時會調用默認的無參數構造函數。通過一些例子程序來理解這些概念。假設我們有以下兩個類。

package com.journaldev.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);
	}

}
package com.journaldev.constructor;

public class Student extends Person {

	private String name;

	public Student() {
		System.out.println("Student Created");
	}

	public Student(int i, String n) {
		super(i); // super class constructor called
		this.name = n;
		System.out.println("Student Created with name = " + n);
	}

}

現在,如果我們像下面這樣創建一個Student對象;

Student st = new Student();

以上代碼的輸出將是:

Person Created
Student Created

所以調用了Student類的無參數構造函數,因為第一個語句中沒有超類調用,所以調用了Person類的無參數或默認構造函數。因此,輸出是如此。如果我們使用Student類的帶參構造函數,如Student st = new Student(34, "Pankaj");,輸出將是:

Person Created with Age = 34
Student Created with name = Pankaj

在這裡,輸出很清楚,因為我們顯式地調用了超類構造函數,所以Java不需要從他們的方面進行任何額外的工作。

Java 複製構造函數

Java複製構造函數以同一類的對象作為參數並創建其副本。有時我們需要另一個對象的副本來進行一些處理。我們可以通過以下方式之一來實現這一點:

  1. 實現 克隆
  2. 提供一個用於對象的 深度拷貝 的實用方法。
  3. 擁有一個複製構造函數

現在讓我們看看如何編寫一個複製構造函數。假設我們有一個類 Fruits 如下。

package com.journaldev.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.journaldev.constructor;

import java.util.ArrayList;
import java.util.List;

public class CopyConstructorTest {

	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 frCopy = fr;
		frCopy.getFruitsList().add("Apple");

		System.out.println(fr.getFruitsList());

		frCopy = new Fruits(fr);
		frCopy.getFruitsList().add("Banana");
		System.out.println(fr.getFruitsList());
		System.out.println(frCopy.getFruitsList());

	}

}

上面程序的輸出是:

[Mango, Orange]
[Mango, Orange, Apple]
[Mango, Orange, Apple]
[Mango, Orange, Apple, Banana]

請注意,當使用複製構造函數時,原始對象和其副本彼此無關,對其中一個的任何修改都不會反映到另一個上。這就是Java中的構造函數的全部。

Source:
https://www.digitalocean.com/community/tutorials/constructor-in-java