Руководство по конструкторам в Java

Руководство по конструкторам в Java

1. Вступление

Конструкторы являются привратникамиobject-oriented design.

В этом руководстве мы увидим, как они действуют как единое место для инициализацииthe internal state создаваемого объекта.

Давайте продвинемся вперед и создадим простой объект, представляющий банковский счет.

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.example.constructors.BankAccount.toString(BankAccount.java:12)
    at com.example.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() теперь вызовет конструктор выше.

Во-вторых, это не требует никаких аргументов. Конструктор такого типа называется no-argument constructor.

Но почему он нам не понадобился в первый раз? Потому что когда мыdon’t explicitly write any constructor, the compiler adds a default, no-argument constructor.

Вот почему мы смогли построить объект в первый раз, хотя мы не писали конструктор явно. По умолчанию конструктор без аргументов просто установит для всех членов ихdefault values.

Для объектов этоnull,, которое привело к исключению, которое мы видели ранее.

4. Параметризованный конструктор

Настоящее преимущество конструкторов заключается в том, что они помогают нам поддерживатьencapsulation при внедрении состояния в объект.

Итак, чтобы сделать что-то действительно полезное с этим банковским счетом, мы должны иметь возможность фактически ввести некоторые начальные значения в объект.

Для этого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;
    }
}

Теперь мы можем сделать что-нибудь полезное с нашим классомBankAccount:

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

Обратите внимание, что в нашем классе теперь есть 2 конструктора. Явный конструктор без аргументов и параметризованный конструктор.

Мы можем создать столько конструкторов, сколько захотим, но мы, вероятно, хотели бы не создавать слишком много конструкторов. Это было бы немного запутанным.

Если мы обнаружим в нашем коде слишком много конструкторов, несколькоCreational Design Patternsмогут оказаться полезными.

5. Конструктор Копий

Конструкторы не должны быть ограничены только инициализацией. Они также могут быть использованы для создания поведения. Imagine that we need to be able to create a new account from an existing one.с

Новая учетная запись должна иметь то же имя, что и старая учетная запись, сегодняшняя дата создания и отсутствие средств. We can do that using a copy constructor:с

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. Цепной конструктор

Конечно, мы можем вывести некоторые параметры конструктора илиgive some of them 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);
}

С ключевым словомthis, мы вызываем другой конструктор.

Мы должны помнить, чтоif we want to chain a superclass constructor we have to use super instead of this.

Также помните, чтоthis or super expression should always be the first statement.

7. Типы значений

Интересное использование конструкторов в Java - созданиеValue Objects. A value object is an object that does not change its internal state after initialization.

That is, the object is immutable. Неизменяемость в Java составляет битnuanced, и при создании объектов следует соблюдать осторожность.

Давайте продолжим и создадим неизменяемый класс:

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 при определении членов класса. Это означает, что каждый из этих членов может быть инициализирован только внутри конструктора класса. Они не могут быть переназначены позже внутри любого другого метода. Мы можем читать эти значения, но не изменять их.

If we create multiple constructors for the Transaction class, each constructor will need to initialize every final variable. Невыполнение этого требования приведет к ошибке компиляции.

8. Заключение

Мы познакомились с различными способами, которыми конструкторы создают объекты. При разумном использовании конструкции образуют основные строительные блоки объектно-ориентированного проектирования в Java.

Как всегда, образцы кода можно найтиover on GitHub.