Oi! Hoje vamos considerar um tópico muito importante que diz respeito aos nossos objetos. Sem exagero, podemos dizer que você usará esse tópico na vida real todos os dias! Estamos falando de Construtores Java. Esta pode ser a primeira vez que você está ouvindo este termo, mas na verdade você já usou construtores. Você simplesmente não percebeu :) Nós nos convencemos disso mais tarde.

O que no mundo são construtores e por que eles são necessários?

Vamos considerar dois exemplos.

public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car bugatti = new Car();
       bugatti.model = "Bugatti Veyron";
       bugatti.maxSpeed = 378;

   }
}
Criamos nosso carro e definimos seu modelo e velocidade máxima. Mas o objeto Car obviamente não teria 2 campos em um projeto real. Por exemplo, pode ter 16 campos!

public class Car {

   String model;// model
   int maxSpeed;// maximum speed
   int wheels;// wheel width
   double engineVolume;// engine volume
   String color;// color
   int productionYear;// production year
   String ownerFirstName;// first name of owner
   String ownerLastName;// last name of owner
   long price;// price
   boolean isNew;// flag indicating whether car is new
   int seatsInTheCar;// number of seats in the car
   String cabinMaterial;// interior material
   boolean insurance;// flag indicating whether car is insured
   String manufacturerCountry;// manufacturer country
   int trunkVolume;// size of the trunk
   int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.productionYear = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.seatsInTheCar = 2;
       bugatti.maxSpeed = 378;
       bugatti.model = "Bugatti Veyron";

   }

}
Criamos um novo objeto Carro . Só tem um problema: temos 16 campos, mas inicializamos apenas 12 ! Veja o código agora e tente encontrar os campos que esquecemos! Não é tão fácil, né? Nesta situação, um programador pode facilmente cometer um erro e falhar ao inicializar algum campo. Como resultado, o programa se comportará incorretamente:

public class Car {

   String model;// model
   int maxSpeed;// maximum speed
   int wheels;// wheel width
   double engineVolume;// engine volume
   String color;// color
   int productionYear;// production year
   String ownerFirstName;// first name of owner
   String ownerLastName;// last name of owner
   long price;// price
   boolean isNew;// flag indicating whether car is new
   int seatsInTheCar;// number of seats in the car
   String cabinMaterial;// interior material
   boolean insurance;// flag indicating whether car is insured
   String manufacturerCountry;// manufacturer country
   int trunkVolume;// size of the trunk
   int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.productionYear = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.seatsInTheCar = 2;
       bugatti.maxSpeed = 378;
       bugatti.model = "Bugatti Veyron";

       System.out.println("Model: Bugatti Veyron. Engine volume: " + bugatti.engineVolume + ". Trunk volume: " + bugatti.trunkVolume + ". Cabin material: " + bugatti.cabinMaterial +
       ". Wheel width: " + bugatti.wheels + ". Purchased in 2018 by Mr. " + bugatti.ownerLastName);

   }

}
Saída do console: Modelo: Bugatti Veyron. Volume do motor: 6.3. Volume do porta-malas: 0. Material da cabine: nulo. Largura da roda: 0. Comprado em 2018 pelo Sr. null Seu comprador, que desistiu de $ 2 milhões pelo carro, obviamente não vai gostar de ser chamado de " Mr. null "! Mas, falando sério, o resultado final é que nosso programa criou um objeto incorretamente: um carro com largura de roda 0 (ou seja, sem rodas), um porta-malas faltando, uma cabine feita de um material desconhecido e, acima de tudo, um proprietário indefinido . Você pode imaginar como esse erro pode "desligar" quando o programa está em execução! Precisamos evitar tais situações de alguma forma. Precisamos restringir nosso programa: ao criar um novo carroobjeto, queremos que os campos, como modelo e velocidade máxima, sejam sempre especificados. Caso contrário, queremos impedir a criação do objeto. Os construtores lidam com essa tarefa com facilidade. Eles receberam esse nome por um motivo. O construtor cria uma espécie de "esqueleto" de classe que deve corresponder a cada novo objeto. Por conveniência, vamos voltar para a versão mais simples da classe Car com dois campos. Considerando nossos requisitos, o construtor da classe Car ficará assim:

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}

// And creating an object now looks like this:

public static void main(String[] args) {
   Car bugatti = new Car("Bugatti Veyron", 378);
}
Observe como um construtor é declarado. É semelhante a um método regular, mas não possui um tipo de retorno. Além disso, o construtor especifica o nome da classe ( Car ) começando com uma letra maiúscula. Além disso, o construtor é usado com uma palavra-chave que é nova para você: this . A palavra-chave this é para indicar um objeto em particular. O código no construtor

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}
pode ser interpretado quase literalmente: "O modelo para este carro (o que estamos criando agora) é o argumento do modelo passado para o construtor. O maxSpeed ​​para este carro (o que estamos criando) é o argumento maxSpeed ​​passado para o construtor." E é só isso que acontece:

public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car("Bugatti Veyron", 378);
       System.out.println(bugatti.model);
       System.out.println(bugatti.maxSpeed);
   }

}
Saída do console: Bugatti Veyron 378 O construtor atribuiu corretamente os valores necessários. Você deve ter notado que um construtor é muito semelhante a um método comum! Então é. Um construtor é realmente um método, mas com características específicas :) Assim como os métodos, passamos argumentos para nosso construtor. E assim como chamar um método, chamar um construtor não funcionará a menos que você os especifique:

public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car(); // Error!
   }
  
}
Você pode ver que o construtor realiza o que estávamos tentando alcançar. Agora você não pode criar um carro sem velocidade ou modelo! A semelhança entre construtores e métodos não termina aqui. Assim como os métodos, os construtores podem ser sobrecarregados. Imagine que você tem 2 gatos de estimação em casa. Você ganhou um deles quando era um gatinho. Mas o segundo você pegou na rua quando já estava crescido, e você não sabe exatamente quantos anos ele tem. Neste caso, queremos que nosso programa seja capaz de criar dois tipos de gatos: aqueles com nome e idade (para o primeiro gato), e aqueles com apenas um nome (para o segundo gato). Para isso, vamos sobrecarregar o construtor:

public class Cat {

   String name;
   int age;

   // For the first cat
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   // For the second cat
   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5);
       Cat streetCatNamedBob = new Cat("Bob");
   }

}
Além do construtor original com os parâmetros "name" e "age", adicionamos mais um com apenas um parâmetro name. Exatamente da mesma forma que sobrecarregamos os métodos nas lições anteriores. Agora podemos criar os dois tipos de gatos :)
Por que precisamos de construtores?  - 2
Lembra que no início da aula dissemos que você já usou construtores sem perceber? Nós quisemos dizer o que dissemos. O fato é que toda classe em Java tem o que chamamos de construtor padrão. Ele não requer nenhum argumento, mas é invocado toda vez que você cria qualquer objeto de qualquer classe.

public class Cat {

   public static void main(String[] args) {

       Cat smudge = new Cat(); // The default constructor is invoked here
   }
}
À primeira vista, é invisível. Criamos um objeto, e daí? Onde o construtor está fazendo alguma coisa aqui? Para vê-lo, vamos escrever explicitamente um construtor vazio para a classe Cat . Vamos exibir alguma frase dentro dele. Se a frase for exibida, o construtor foi invocado.

public class Cat {

   public Cat() {
       System.out.println("A cat has been created!");
   }

   public static void main(String[] args) {

       Cat smudge = new Cat(); // The default constructor is invoked here
   }
}
Saída do console: Um gato foi criado! Aí está a confirmação! O construtor padrão está sempre presente de forma invisível em suas classes. Mas você precisa saber mais uma coisa sobre isso. O construtor padrão é eliminado de uma classe quando você cria um construtor com argumentos. Na verdade, já vimos a prova disso acima. Foi neste código:

public class Cat {

   String name;
   int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat(); //Error!
   }
}
Não poderíamos criar um Cat sem nome e idade, porque declaramos um construtor Cat com parâmetros string e int . Isso fez com que o construtor padrão desaparecesse imediatamente da classe. Portanto, lembre-se de que, se precisar de vários construtores em sua classe, incluindo um construtor sem argumentos, você terá que declará-lo separadamente. Por exemplo, suponha que estamos criando um programa para uma clínica veterinária. Nossa clínica quer fazer boas ações e ajudar gatinhos sem-teto cujos nomes e idades são desconhecidos. Então nosso código deve ficar assim:

public class Cat {

   String name;
   int age;

   // For cats with owners
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   // For street cats
   public Cat() {
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5);
       Cat streetCat = new Cat();
   }
}
Agora que escrevemos um construtor padrão explícito, podemos criar os dois tipos de gatos :) Como em qualquer método, a ordem dos argumentos passados ​​para um construtor é muito importante. Vamos trocar os argumentos name e age em nosso construtor.

public class Cat {

   String name;
   int age;

   public Cat(int age, String name) {
       this.name = name;
       this.age = age;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 10); // Error!
   }
}
Um erro! O construtor estipula claramente que, quando um objeto Cat é criado, deve ser passado um número e uma string, nesta ordem. Portanto, nosso código não funciona. Certifique-se de lembrar disso e mantê-lo em mente ao declarar suas próprias classes:

public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}

public Cat(int age, String name) {
   this.age = age;
   this.name = name;
}
Estes são dois construtores totalmente diferentes! Se fôssemos expressar em uma única frase a resposta para a pergunta "Por que preciso de um construtor?", poderíamos dizer: "Para garantir que os objetos sempre tenham um estado válido". Quando você usa construtores, todas as suas variáveis ​​serão inicializadas corretamente. Seus programas não terão nenhum carro com velocidade 0 ou qualquer outro objeto "inválido". Seu principal benefício é para o programador. Se você inicializar os campos manualmente (depois de criar um objeto), há um grande risco de perder alguma coisa e introduzir um bug. Mas isso não acontecerá com um construtor: se você não passar todos os argumentos necessários ou passar os tipos errados de argumentos, o compilador registrará imediatamente um erro. Também devemos dizer separadamente que você não deve colocar seu programa' s lógica dentro de um construtor. É para isso que servem os métodos. Os métodos são onde você deve definir toda a funcionalidade necessária. Vamos ver por que adicionar lógica a um construtor é uma má ideia:

public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Our car factory is called " + this.name);
   System.out.println("It was founded " + this.age + " years ago" );
   System.out.println("Since that time, it has produced " + this.carsCount +  " cars");
   System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}

   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Ford", 115 , 50000000);
   }
}
Temos uma classe CarFactory que descreve a fábrica de automóveis. Dentro do construtor, inicializamos todos os campos e incluímos alguma lógica: exibimos algumas informações sobre a fábrica. Parece que não há nada de ruim nisso. O programa funciona bem. Saída do console: Nossa fábrica de carros se chama Ford Foi fundada há 115 anos Desde então, produziu 50000000 carros Em média, produz 434782 carros por ano Mas, na verdade, construímos uma mina atrasada. E esse tipo de código pode facilmente levar a erros. Suponha que agora não estamos falando da Ford, mas de uma nova fábrica chamada "Amigo Motors", que existe há menos de um ano e produziu 1.000 carros:

public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Our car factor is called " + this.name);
   System.out.println("It was founded " + this.age + " years ago" );
   System.out.println("Since that time, it has produced " + this.carsCount +  " cars");
   System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}


   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Amigo Motors", 0 , 1000);
   }
}
Saída do console: Nossa fábrica de carros é chamada Amigo Motors Exceção no thread "main" java.lang.ArithmeticException: / por zero Foi fundada há 0 anos Desde então, produziu 1.000 carros na CarFactory. (CarFactory.java:15) em CarFactory.main(CarFactory.java:23) Processo finalizado com código de saída 1 Estrondo! O programa termina com algum tipo de erro incompreensível. Você pode tentar adivinhar a causa? O problema está na lógica que colocamos no construtor. Mais especificamente, esta linha:

System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
Aqui você está realizando um cálculo e dividindo o número de carros produzidos pela idade da fábrica. E como nossa fábrica é nova (ou seja, tem 0 anos), dividimos por 0, o que não podemos fazer em matemática. Consequentemente, o programa termina com um erro.

O que deveríamos ter feito?

Coloque toda a lógica em um método separado. Vamos chamá-lo de printFactoryInfo() . Você pode passar um objeto CarFactory para ele como um argumento. Você pode colocar toda a lógica lá e, simultaneamente, lidar com possíveis erros (como o nosso envolvendo anos zero). Cada um na sua. Os construtores são necessários para definir o estado do objeto válido. Temos métodos para lógica de negócios. Não misture um com o outro. Para reforçar o que você aprendeu, sugerimos que você assista a uma vídeo aula do nosso Curso de Java