Um guia para construtores em Java

Um guia para construtores em Java

1. Introdução

Os construtores são os guardiões deobject-oriented design.

Neste tutorial, veremos como eles atuam como um único local a partir do qual inicializarthe internal state do objeto que está sendo criado.

Vamos seguir em frente e criar um objeto simples que representa uma conta bancária.

2. Configurando uma conta bancária

Imagine que precisamos criar uma classe que represente uma conta bancária. Ele conterá um nome, data de criação e saldo.

Além disso, vamos substituir o métodotoString para imprimir os detalhes no console:

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

Agora, esta classe contém todos os campos necessários exigidos para armazenar informações sobre uma conta bancária, mas ainda não contém um construtor.

Isso significa que, se criarmos um novo objeto, os valores do campo não serão inicializados:

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

Executar o métodotoString acima resultará em uma exceção porque os objetosnameeopened ainda sãonull:

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

3. Um construtor sem argumento

Vamos consertar isso com um construtor:

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

Observe algumas coisas sobre o construtor que acabamos de escrever. Primeiro, é um método, mas não tem tipo de retorno. Isso ocorre porque um construtor retorna implicitamente o tipo do objeto que ele cria. Chamar new BankAccount() agora chamará o construtor acima.

Em segundo lugar, não há argumentos. Esse tipo específico de construtor é chamado de no-argument constructor.

Por que não precisamos pela primeira vez? É porque quando nósdon’t explicitly write any constructor, the compiler adds a default, no-argument constructor.

É por isso que fomos capazes de construir o objeto da primeira vez, embora não tenhamos escrito um construtor explicitamente. O padrão, nenhum construtor de argumento irá simplesmente definir todos os membros para seusdefault values.

Para objetos, énull, que resultou na exceção que vimos anteriormente.

4. Um construtor parametrizado

Agora, um benefício real dos construtores é que eles nos ajudam a manterencapsulation ao injetar estado no objeto.

Portanto, para fazer algo realmente útil com essa conta bancária, precisamos ser capazes de realmente injetar alguns valores iniciais no objeto.

Para fazer isso,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;
    }
}

Agora podemos fazer algo útil com nossa classeBankAccount:

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

Observe que nossa classe agora tem 2 construtores. Um construtor explícito, sem argumento e um construtor parametrizado.

Podemos criar quantos construtores quisermos, mas provavelmente não gostaríamos de criar muitos. Isso seria um pouco confuso.

Se encontrarmos muitos construtores em nosso código, algunsCreational Design Patterns podem ser úteis.

5. Um construtor de cópias

Os construtores não precisam se limitar apenas à inicialização. Eles também podem ser usados ​​para criar comportamentos. Imagine that we need to be able to create a new account from an existing one.

A nova conta deve ter o mesmo nome da conta antiga, a data de criação de hoje e sem fundos. We can do that using a copy constructor:

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

Agora, temos o seguinte comportamento:

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. Um Construtor Acorrentado

Claro, podemos ser capazes de inferir alguns dos parâmetros do construtor ougive some of them default values.

Por exemplo, podemos apenas criar uma nova conta bancária com apenas o nome.

Então, vamos criar um construtor com um parâmetroname e dar aos outros parâmetros os valores padrão:

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

Com a palavra-chavethis,, estamos chamando o outro construtor.

Temos que lembrar queif we want to chain a superclass constructor we have to use super instead of this.

Além disso, lembre-se de quethis or super expression should always be the first statement.

7. Tipos de valor

Um uso interessante de construtores em Java é na criação deValue Objects. A value object is an object that does not change its internal state after initialization.

That is, the object is immutable. A imutabilidade em Java é um pouconuancede deve ter cuidado ao criar objetos.

Vamos em frente e criar uma classe imutável:

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

Observe que agora usamos a palavra-chavefinal ao definir os membros da classe. Isso significa que cada um desses membros só pode ser inicializado no construtor da classe. Eles não podem ser reatribuídos posteriormente em nenhum outro método. Podemos ler esses valores, mas não alterá-los.

If we create multiple constructors for the Transaction class, each constructor will need to initialize every final variable. Não fazer isso resultará em um erro de compilação.

8. Conclusão

Fizemos um tour pelas diferentes maneiras como os construtores constroem objetos. Quando usadas criteriosamente, as construções formam os blocos de construção básicos do design orientado a objetos em Java.

Como sempre, as amostras de código podem ser encontradasover on GitHub.