Construtores - Chamar Métodos a Partir de Construtores

Não chame métodos que podem ser sobrescritos a partir de construtores. Ao criar um objeto de subclasse, isso pode fazer com
que um método sobrescrito seja chamado antes de o objeto de subclasse ser totalmente inicializado.
Lembre-se de que ao construir um objeto de subclasse, o construtor primeiro chama um dos construtores da superclasse direta.
Se o construtor da superclasse chamar um método que pode ser sobrescrito, a versão da subclasse desse método será chamada pelo
construtor da superclasse, antes de o corpo do construtor da subclasse ter a chance de executar. Isso pode levar a erros sutis difíceis
de detectar se o método da subclasse que foi chamado depender de inicialização que ainda não foi realizada no corpo do construtor
da subclasse.
É aceitável chamar um método static a partir de um construtor. Por exemplo, um construtor e um método set muitas vezes
fazem a mesma validação para uma variável de instância particular. Se o código de validação for curto, é aceitável duplicá-lo no
construtor e no método set. Se uma validação mais longa for necessária, defina um método de validação static (normalmente um
método auxiliar private) e, então, o chame do construtor e método set. Também é aceitável que um construtor chame um método
de instância final, desde que o método não chame direta ou indiretamente um método de instância que pode ser sobrescrito.

Compreender e seguir essas boas práticas é importante para garantir que o código seja robusto e funcione corretamente em todas as situações. Quando estamos lidando com herança e construtores em Java, é especialmente importante ter cuidado para não chamar métodos que possam ser sobrescritos antes que o objeto da subclasse esteja completamente inicializado.

Um exemplo de código que viola essa prática é o seguinte:

public class Animal {
  public Animal() {
    fazerSom();
  }

  public void fazerSom() {
    System.out.println("O animal fez um som.");
  }
}

public class Cachorro extends Animal {
  private String nome;

  public Cachorro(String nome) {
    this.nome = nome;
  }

  public void fazerSom() {
    System.out.println(nome + " latiu.");
  }
}

Nesse exemplo, a classe Animal tem um construtor que chama o método fazerSom(). A classe Cachorro sobrescreve esse método para fazer o cachorro latir. No entanto, se um objeto Cachorro for criado, o construtor de Animal será chamado primeiro, e isso fará com que o cachorro tente latir antes que seu nome tenha sido inicializado. Isso pode levar a erros ou comportamento inesperado.

Para evitar esse problema, podemos modificar o construtor de Animal para não chamar fazerSom(), ou podemos modificar fazerSom() para ser um método final, que não pode ser sobrescrito pelas subclasses. Outra opção seria passar o nome do cachorro para o construtor de Animal, para que ele possa ser inicializado antes de o método fazerSom() ser chamado.