构造函数在创建对象时用来初始化对象。 它与类具有相同的名称,并且在语法上与方法类似。但是,构造函数没有返回值类型。
通常,使用构造函数为类定义的实例变量提供初始值,或执行创建完全形成的对象所需的其他启动过程。
所有类都有构造函数,无论是否定义了构造函数,因为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]
请注意,当使用复制构造函数时,原始对象和它的副本彼此无关,并且其中一个中的任何修改都不会反映到其他对象中。