1. 序章

コンストラクターは、オブジェクト指向設計のゲートキーパーです。

このチュートリアルでは、作成中のオブジェクトの内部状態を初期化する単一の場所としてそれらがどのように機能するかを確認します。

先に進んで、銀行口座を表す単純なオブジェクトを作成しましょう。

2. 銀行口座の設定

銀行口座を表すクラスを作成する必要があると想像してください。 名前、作成日、残高が含まれます。

また、 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);
    }
}

現在、このクラスには、銀行口座に関する情報を格納するために必要なすべてのフィールドが含まれていますが、コンストラクターはまだ含まれていません。

これは、新しいオブジェクトを作成した場合、フィールド値が初期化されないことを意味します。

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

上記のtoStringメソッドを実行すると、オブジェクトnameおよびopenedがまだ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. 引数なしのコンストラクタ

コンストラクターでそれを修正しましょう:

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

先ほど作成したコンストラクターについていくつか注意してください。 まず、これはメソッドですが、リターンタイプはありません。 これは、コンストラクターが作成するオブジェクトのタイプを暗黙的に返すためです。 new BankAccount()を呼び出すと、上記のコンストラクターが呼び出されます。

第二に、それは引数を取りません。 この特定の種類のコンストラクターは、o引数コンストラクターと呼ばれます。

しかし、なぜ初めてそれが必要でなかったのですか? これは、コンストラクターを明示的に記述しない場合、コンパイラーがデフォルトの引数なしのコンストラクターを追加するためです。

これが、コンストラクターを明示的に記述していなくても、オブジェクトを初めて構築できた理由です。 デフォルトの引数なしコンストラクターは、すべてのメンバーをデフォルト値に設定するだけです。

オブジェクトの場合、これは null、であり、前に見た例外が発生しました。

4. パラメータ化されたコンストラクタ

現在、コンストラクターの本当の利点は、オブジェクトに状態を挿入するときにカプセル化を維持するのに役立つことです。

したがって、この銀行口座で本当に役立つことを行うには、実際にいくつかの初期値をオブジェクトに挿入できる必要があります。

これを行うには、パラメーター化されたコンストラクター、つまり、いくつかの引数を取るコンストラクターを記述しましょう

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

これで、BankAccountクラスで何か便利なことができます。

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

クラスに2つのコンストラクターがあることに注意してください。 明示的な引数なしのコンストラクターとパラメーター化されたコンストラクター。

コンストラクターはいくつでも作成できますが、作成しすぎないようにしたいと思います。 これは少し混乱するでしょう。

コード内にコンストラクターが多すぎる場合は、いくつかの Creation DesignPatternsが役立つ場合があります。

5. コピーコンストラクタ

コンストラクターは、初期化のみに限定される必要はありません。 また、他の方法でオブジェクトを作成するために使用することもできます。 既存のアカウントから新しいアカウントを作成できるようにする必要があると想像してください。

新しいアカウントは、古いアカウントと同じ名前で、今日の作成日であり、資金はありません。 コピーコンストラクターを使用してこれを行うことができます:

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

これで、次の動作が発生します。

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. 連鎖コンストラクター

もちろん、コンストラクターパラメーターの一部を推測したり、それらの一部にデフォルト値を指定したりできる場合があります。

たとえば、名前だけで新しい銀行口座を作成することができます。

それでは、 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);
}

キーワードthisを使用して、他のコンストラクターを呼び出しています。

スーパークラスコンストラクターをチェーンする場合は、これの代わりにsuperを使用する必要があることを覚えておく必要があります。

また、この式またはスーパー式は常に最初のステートメントである必要があることに注意してください。

7. 値型

Javaでのコンストラクターの興味深い使用法は、 ValueObjectsの作成です。 値オブジェクトは、初期化後に内部状態を変更しないオブジェクトです。

つまり、オブジェクトは不変です。 Javaの不変性は少し微妙なであり、オブジェクトを作成するときは注意が必要です。

先に進んで、不変のクラスを作成しましょう。

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;
    }
}

クラスのメンバーを定義するときにfinalキーワードを使用することに注意してください。 つまり、これらの各メンバーは、クラスのコンストラクター内でのみ初期化できます。 後で他のメソッド内で再割り当てすることはできません。 これらの値を読み取ることはできますが、変更することはできません。

Transactionクラスに複数のコンストラクターを作成する場合、各コンストラクターはすべての最終変数を初期化する必要があります。そうしないと、コンパイルエラーが発生します。

8. 結論

コンストラクターがオブジェクトを作成するさまざまな方法について説明しました。 慎重に使用すると、コンストラクトはJavaのオブジェクト指向設計の基本的な構成要素を形成します。

いつものように、コードサンプルはGitHubで見つけることができます。