Java 构造函数

更新于 2025-12-25

Jakob Jenkov 2021-03-09

Java 构造函数是一种特殊方法,当对象被实例化时会被调用。换句话说,当你使用 new 关键字创建对象时,就会调用构造函数。Java 构造函数的目的是在对象被使用之前对其进行初始化。

本教程将更详细地探讨 Java 构造函数。

下面是一个简单的示例,它创建一个对象,从而导致类的构造函数被调用:

MyClass myClassObj = new MyClass();

这个示例会创建一个新的 MyClass 对象,并调用 MyClass 的无参构造函数(no-arg constructor)。稍后你将了解什么是无参构造函数。

Java 类的构造函数用于初始化该类的实例(对象)。通常,构造函数会初始化对象中需要初始化的字段。Java 构造函数也可以接受参数,以便在对象创建时就对字段进行初始化。


在 Java 中定义构造函数

下面是一个简单的 Java 构造函数声明示例。该示例展示了一个带有单个构造函数的简单 Java 类:

public class MyClass {
    public MyClass() {
    }
}

其中构造函数是这一部分:

public MyClass() {
}

Java 构造函数声明的第一部分是访问修饰符(access modifier)。访问修饰符的含义与方法和字段中的相同,用于决定哪些类可以访问(调用)该构造函数。关于访问修饰符的更多内容,请参阅 Java 访问修饰符

第二部分是构造函数所属的类名。使用类名作为构造函数名,是向 Java 编译器表明这是一个构造函数的方式。同时请注意,构造函数没有返回类型(不像普通方法那样有返回值类型)。

第三部分是构造函数可以接受的参数列表。这些参数在构造函数名后的圆括号 () 内声明。上面的示例中没有声明任何参数。稍后我们会展示带参数的构造函数声明示例。

第四部分是构造函数体,定义在参数列表后的花括号 {} 内。上面的示例中,构造函数体为空,因此被称为“空构造函数”。


构造函数重载 —— 为 Java 类定义多个构造函数

只要签名(即所接受的参数)不同,一个类就可以拥有多个构造函数。你可以根据需要定义任意数量的构造函数。当一个 Java 类包含多个构造函数时,我们称该构造函数被重载(overloaded),即构造函数有多个版本。

下面是一个 Java 构造函数重载的示例:

public class MyClass {
    private int number = 0;

    public MyClass() {
    }

    public MyClass(int theNumber) {
        this.number = theNumber;
    }
}

上面的 Java 类包含两个构造函数:

  • 第一个构造函数是无参构造函数(不接受任何参数)。
  • 第二个构造函数接受一个 int 类型的参数,并在构造函数体内将该参数值赋给字段 number,从而完成字段的初始化。

构造函数中字段名前的 this 关键字不是必需的,但它向编译器明确指出这里引用的是类的字段。这一点在后面的“构造函数参数”一节中有更详细的解释。


默认无参构造函数

你并非必须为类显式定义构造函数。但如果你没有定义任何构造函数,Java 编译器会自动为你插入一个默认的无参构造函数。因此,一旦类被编译,它至少会有一个无参构造函数。

但如果你自己定义了至少一个构造函数,Java 编译器就不会再插入默认的无参构造函数


构造函数参数

如前所述,Java 构造函数可以接受参数。这些参数可用于初始化新创建对象的内部状态(字段)。下面是一个示例:

public class Employee {
    private String firstName = null;
    private String lastName = null;
    private int birthYear = 0;

    public Employee(String first, String last, int year) {
        firstName = first;
        lastName = last;
        birthYear = year;
    }
}

在这个示例中,加粗部分(原文如此)是 Java 构造函数声明。可以看到,它声明了三个参数:firstlastyear。在构造函数体内,这三个参数的值被分别赋给 Employee 对象的字段。

参数声明中的换行符是可选的,Java 编译器会忽略它们。你也可以将参数写在一行中:

public Employee(String first, String last, int year) {
    firstName = first;
    lastName = last;
    birthYear = year;
}

要调用这个接受三个参数的构造函数,你可以这样实例化 Employee 对象:

Employee employee = new Employee("Jack", "Daniels", 2000);

参数在等号右侧的类名后的圆括号中传入。对象创建后,构造函数被执行,字段将被初始化为传入的参数值。

如果构造函数参数与字段同名,Java 编译器将无法区分你指的是参数还是字段。默认情况下,若参数(或局部变量)与字段同名,则参数(或局部变量)会“遮蔽”(shadow)字段。例如:

public class Employee {
    private String firstName = null;
    private String lastName = null;
    private int birthYear = 0;

    public Employee(String firstName, String lastName, int birthYear) {
        firstName = firstName;     // 错误!参数赋值给自己
        lastName = lastName;
        birthYear = birthYear;
    }
}

在上述构造函数中,firstName 等标识符指的是参数,而不是同名的字段。因此,字段从未被初始化。

为明确告诉编译器你指的是字段而非参数,需在字段名前加上 this.。修改后的正确写法如下:

public class Employee {
    private String firstName = null;
    private String lastName = null;
    private int birthYear = 0;

    public Employee(String firstName, String lastName, int birthYear) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.birthYear = birthYear;
    }
}

现在,Employee 的字段就能在构造函数中被正确初始化了。


调用构造函数

当你创建一个类的新实例时,就会调用该类的构造函数。例如:

MyClass myClassVar = new MyClass();

此示例调用了前面定义的 MyClass 的无参构造函数。

如果你想向构造函数传递参数,只需在类名后的圆括号中包含参数即可:

MyClass myClassVar = new MyClass(1975);

此示例向接受一个 int 参数的 MyClass 构造函数传递了一个参数。


从一个构造函数中调用另一个构造函数

在 Java 中,可以从一个构造函数内部调用同一个类的另一个构造函数。此时需使用 this 关键字。例如:

public class Employee {
    private String firstName = null;
    private String lastName = null;
    private int birthYear = 0;

    public Employee(String first, String last, int year) {
        firstName = first;
        lastName = last;
        birthYear = year;
    }

    public Employee(String first, String last) {
        this(first, last, -1);
    }
}

注意第二个构造函数中的这行代码:

this(first, last, -1);

this 后跟圆括号和参数,表示调用当前类中的另一个构造函数。具体调用哪一个,取决于传入的参数。在此例中,它调用了第一个构造函数。


调用父类的构造函数

在 Java 中,一个类可以继承(extend)另一个类。子类(subclass)必须在其构造函数中调用父类(superclass)的某个构造函数。即使子类的构造函数又调用了本类的另一个构造函数,最终被调用的那个构造函数也必须调用父类的构造函数。

看下面两个类。Car 类继承自 Vehicle 类:

public class Vehicle {
    private String regNo = null;

    public Vehicle(String no) {
        this.regNo = no;
    }
}

public class Car extends Vehicle {
    private String brand = null;

    public Car(String br, String no) {
        super(no);
        this.brand = br;
    }
}

注意 Car 类的构造函数中的这行代码:

super(no);

super 关键字代表当前类的直接父类。当 super 后跟圆括号时,表示调用父类的某个构造函数。此处调用了 Vehicle 类的构造函数。

由于 Car 继承自 Vehicle,所有 Car 的构造函数都必须调用 Vehicle 的某个构造函数。


Java 构造函数的访问修饰符

构造函数的访问修饰符决定了应用程序中哪些类可以调用该构造函数。 例如,如果一个构造函数被声明为 protected,那么只有同一包中的类或该类的子类才能调用它。

一个类可以有多个构造函数,每个构造函数可以有不同的访问修饰符。因此,某些构造函数可能对所有类可见,而另一些则仅对同一包中的类、子类,甚至仅对本类自身(私有构造函数)可见。


从构造函数中抛出异常

Java 构造函数可以抛出异常。例如:

public class Car {
    public Car(String brand) throws Exception {
        if (brand == null) {
            throw new Exception("The brand parameter cannot be null!");
        }
    }
}

注意构造函数声明中的 throws Exception 部分,它声明该构造函数可能会抛出 Exception。如果发生异常,所创建的 Car 实例将是无效的。

调用该构造函数的示例如下:

Car car = null;
try {
    car = new Car("Mercedes");
    // 使用 car 对象
} catch (Exception e) {
    // 处理异常
}

如果构造函数抛出异常,car 变量将不会被赋值为新创建的对象引用,而是保持为 null

让构造函数在检测到无效输入参数时抛出异常,是一种防止对象处于非法状态的有效方式。