A Guide to Constructors in Java – Java中的构造函数指南

最后修改: 2018年 11月 22日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Constructors are the gatekeepers of object-oriented design.

构造函数是面向对象设计的守门员。

In this tutorial, we’ll see how they act as a single location from which to initialize the internal state of the object being created.

在本教程中,我们将看到它们如何作为一个单一的位置来初始化被创建对象的内部状态

Let’s forge ahead and create a simple object that represents a bank account.

让我们继续前进,创建一个简单的对象,代表一个银行账户。

2. Setting Up a Bank Account

2.开设银行账户

Imagine that we need to create a class that represents a bank account. It’ll contain a Name, Date of Creation and Balance.

想象一下,我们需要创建一个代表银行账户的类。它将包含一个名称、创建日期和余额。

Also, let’s override the toString method to print the details to the console:

另外,让我们覆盖toString方法,将细节打印到控制台。

class BankAccount {
    String name;
    LocalDateTime opened;
    double balance;
    
    @Override
    public String toString() {
        return String.format("%s, %s, %f", 
          this.name, this.opened.toString(), this.balance);
    }
}

Now, this class contains all of the necessary fields required to store information about a bank account, but it doesn’t contain a constructor yet.

现在,这个类包含了存储银行账户信息所需的所有必要字段,但它还没有包含一个构造函数。

This means that if we create a new object, the field values wouldn’t be initialized:

这意味着,如果我们创建一个新的对象,字段值将不会被初始化:

BankAccount account = new BankAccount();
account.toString();

Running the toString method above will result in an exception because the objects name and opened are still null:

运行上面的toString方法将导致一个异常,因为对象nameopened仍然是null

java.lang.NullPointerException
    at com.baeldung.constructors.BankAccount.toString(BankAccount.java:12)
    at com.baeldung.constructors.ConstructorUnitTest
      .givenNoExplicitContructor_whenUsed_thenFails(ConstructorUnitTest.java:23)

3. A No-Argument Constructor

3.一个没有参数的构造函数

Let’s fix that with a constructor:

让我们用一个构造函数来解决这个问题。

class BankAccount {
    public BankAccount() {
        this.name = "";
        this.opened = LocalDateTime.now();
        this.balance = 0.0d;
    }
}

Notice a few things about the constructor which we just wrote. First, it’s a method, but it has no return type. That’s because a constructor implicitly returns the type of the object that it creates. Calling new BankAccount() now will call the constructor above.

请注意我们刚刚写的构造函数的几件事。首先,它是一个方法,但它没有返回类型。这是因为构造函数隐含地返回它所创建对象的类型。现在调用 new BankAccount()将调用上面的构造函数。

Secondly, it takes no arguments. This particular kind of constructor is called a no-argument constructor.

其次,它不需要参数。这种特殊的构造函数被称为no-argument构造函数

Why didn’t we need it for the first time, though? It’s because when we don’t explicitly write any constructor, the compiler adds a default, no-argument constructor.

不过,为什么我们第一次不需要它呢?这是因为当我们没有明确地写任何构造函数时,编译器会添加一个默认的、无参数的构造函数

This is why we were able to construct the object the first time, even though we didn’t write a constructor explicitly. The default, no argument constructor will simply set all members to their default values.

这就是为什么我们能够在第一次构建对象,尽管我们没有明确地写一个构造函数。默认的、没有参数的构造函数将简单地把所有成员设置为其默认值

For objects, that’s null, which resulted in the exception that we saw earlier.

对于对象来说,那就是null,,这导致了我们之前看到的异常。

4. A Parameterized Constructor

4.一个参数化的构造函数

Now, a real benefit of constructors is that they help us maintain encapsulation when injecting state into the object.

现在,构造函数的一个真正的好处是,在向对象注入状态时,它们可以帮助我们保持封装

So, to do something really useful with this bank account, we need to be able to actually inject some initial values into the object.

因此,为了对这个银行账户做一些真正有用的事情,我们需要能够真正向对象注入一些初始值。

To do that, let’s write a parameterized constructor, that is, a constructor that takes some arguments:

要做到这一点,让我们写一个参数化的构造函数,也就是说,一个需要一些参数的构造函数

class BankAccount {
    public BankAccount() { ... }
    public BankAccount(String name, LocalDateTime opened, double balance) {
        this.name = name;
        this.opened = opened;
        this.balance = balance;
    }
}

Now we can do something useful with our BankAccount class:

现在我们可以用我们的BankAccount类做一些有用的事情。

    LocalDateTime opened = LocalDateTime.of(2018, Month.JUNE, 29, 06, 30, 00);
    BankAccount account = new BankAccount("Tom", opened, 1000.0f); 
    account.toString();

Notice, that our class now has 2 constructors. An explicit, no argument constructor and a parameterized constructor.

注意,我们的类现在有两个构造函数。一个显式的、无参数的构造函数和一个参数化的构造函数。

We can create as many constructors as we like, but we probably would like not to create too many. This would be a little confusing.

我们可以创建任意多的构造函数,但我们可能希望不要创建太多的构造函数。这将是一个有点混乱的问题。

If we find too many constructors in our code, a few Creational Design Patterns might be helpful.

如果我们发现我们的代码中有太多的构造函数,一些休闲设计模式可能会有帮助。

5. A Copy Constructor

5.一个复制构造函数

Constructors need not be limited to initialization alone. They can also be used to create objects in other ways. Imagine that we need to be able to create a new account from an existing one.

构造函数不需要仅仅局限于初始化。它们也可以被用来以其他方式创建对象。想象一下,我们需要从一个现有的账户中创建一个新的账户。

The new account should have the same name as the old account, today’s date of creation and no funds. We can do that using a copy constructor:

新账户的名称应该与旧账户相同,今天的创建日期,没有资金。我们可以使用复制构造函数来实现这一点:

public BankAccount(BankAccount other) {
    this.name = other.name;
    this.opened = LocalDateTime.now();
    this.balance = 0.0f;
}

Now we have the following behavior:

现在我们有以下行为。

LocalDateTime opened = LocalDateTime.of(2018, Month.JUNE, 29, 06, 30, 00);
BankAccount account = new BankAccount("Tim", opened, 1000.0f);
BankAccount newAccount = new BankAccount(account);

assertThat(account.getName()).isEqualTo(newAccount.getName());
assertThat(account.getOpened()).isNotEqualTo(newAccount.getOpened());
assertThat(newAccount.getBalance()).isEqualTo(0.0f);

6. A Chained Constructor

6.一个链式构造器

Of course, we may be able to infer some of the constructor parameters or give some of them default values.

当然,我们也许可以推断出一些构造函数参数,或者赋予其中一些参数默认值。

For example, we could just create a new bank account with only the name.

例如,我们可以直接创建一个新的银行账户,只写上名字。

So, let’s create a constructor with a name parameter and give the other parameters default values:

因此,让我们创建一个带有name参数的构造函数,并赋予其他参数默认值。

public BankAccount(String name, LocalDateTime opened, double balance) {
    this.name = name;
    this.opened = opened;
    this.balance = balance;
}
public BankAccount(String name) {
    this(name, LocalDateTime.now(), 0.0f);
}

With the keyword this, we’re calling the other constructor.

通过关键词this,我们正在调用另一个构造函数。

We have to remember that if we want to chain a superclass constructor we have to use super instead of this.

我们必须记住,如果我们想要连锁一个超类构造函数,我们必须使用super而不是this

Also, remember that this or super expression should always be the first statement.

另外,请记住,thissuper表达式应该始终是第一个语句。

7. Value Types

7.价值类型

An interesting use of constructors in Java is in the creation of Value Objects. A value object is an object that does not change its internal state after initialization.

构造函数在Java中的一个有趣的用途是创建值对象值对象是一个在初始化后不改变其内部状态的对象。

That is, the object is immutable. Immutability in Java is a bit nuanced and care should be taken when crafting objects.

也就是说,该对象是不可变的。Java中的不可变性有点nuanced,在制作对象时应注意。

Let’s go ahead and create an immutable class:

让我们继续前进,创建一个不可变的类。

class Transaction {
    final BankAccount bankAccount;
    final LocalDateTime date;
    final double amount;

    public Transaction(BankAccount account, LocalDateTime date, double amount) {
        this.bankAccount = account;
        this.date = date;
        this.amount = amount;
    }
}

Notice, that we now use the final keyword when defining the members of the class. This means that each of those members can only be initialized within the constructor of the class. They cannot be reassigned later on inside any other method. We can read those values, but not change them.

注意,我们现在在定义类的成员时使用了final关键字。这意味着每个成员只能在类的构造函数中被初始化。它们不能在以后的任何其他方法中被重新分配。我们可以读取这些值,但不能改变它们。

If we create multiple constructors for the Transaction class, each constructor will need to initialize every final variable. Not doing so will result in a compilation error.

如果我们为Transaction类创建多个构造函数,每个构造函数都需要初始化每个最终变量。不这样做将导致编译错误。

8. Conclusion

8.结语

We’ve taken a tour through the different ways in which constructors build objects. When used judiciously, constructs form the basic building blocks of Object-Oriented design in Java.

我们已经参观了构造函数构建对象的不同方式。如果明智地使用,构造函数构成了Java中面向对象设计的基本构件。

As always, code samples can be found over on GitHub.

一如既往,代码样本可以在GitHub上找到