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方法访问和初始化。

无参构造函数

构造函数没有任何参数称为无参数构造函数。 这类似于重写默认构造函数,并用于执行一些预初始化工作,例如检查资源、网络连接、日志记录等。 让我们快速看一下 Java 中的无参数构造函数。

package com.journaldev.constructor;

public class Data {
        //无参数构造函数
	public Data() {
		System.out.println("No-Args Constructor");
	}
	public static void main(String[] args) {
		Data d = new Data();
	}
}

现在当我们调用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类的无参数构造函数,因为在第一个语句中没有super调用,所以调用了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