Java Construtores
Até aqui, criar uma fruta levava três linhas: new Fruta(), depois o nome, depois o preço. E nada impedia de esquecer uma delas. O construtor resolve isso: é um bloco especial da classe que roda no momento em que o objeto nasce, e pode exigir os dados necessários logo no new: new Fruta("maçã", 7.9).
- O que é um construtor e como ele difere de um método.
- O construtor padrão que o Java cria sozinho, e quando ele deixa de existir.
- Construtores com parâmetros, para cada objeto nascer pronto.
- Vários construtores na mesma classe (sobrecarga) e o
this(…), que encadeia um no outro. - Os erros reais:
constructor … cannot be applied to given types, o construtor comvoide orecursive constructor invocation.
A ideia antes do código: a maternidade
Quando um bebê nasce, ele não sai da maternidade "em branco": já sai com nome, data de nascimento e uma pulseira de identificação. Esse procedimento acontece uma vez, no nascimento, e é obrigatório.
O construtor é o procedimento de nascimento de um objeto. Ele roda automaticamente a cada new, uma única vez por objeto, e serve para deixar o objeto pronto para uso.
O construtor padrão
Você já usou construtores sem saber. Em new Fruta(), os parênteses depois de Fruta são uma chamada de construtor. Se a classe não escreve nenhum, o Java cria sozinho um construtor vazio, sem parâmetros, chamado construtor padrão. Ele não faz nada além de deixar os atributos com os valores padrão.
Você pode escrever esse construtor sem parâmetros por conta própria, para ver quando ele roda:
class Fruta {
String nome;
double preco;
Fruta() {
System.out.println("Uma fruta nova nasceu!");
}
}
public class Main {
public static void main(String[] args) {
Fruta a = new Fruta();
Fruta b = new Fruta();
System.out.println(a.nome + ", " + a.preco);
}
}
$ javac Main.java && java Main Uma fruta nova nasceu! « o new da linha 12 Uma fruta nova nasceu! « o new da linha 13 null, 0.0 « os atributos continuam com os valores padrão
- Fruta
- O nome do construtor é exatamente o nome da classe, com a mesma maiúscula.
- (sem tipo de retorno)
- Repare: não há
void, nemint, nem nada antes do nome. Um construtor não devolve valor; quem "devolve" o objeto pronto é onew. É isso que o diferencia de um método. - ()
- A lista de parâmetros. Vazia aqui.
- { … }
- O corpo, executado a cada
new Fruta().
Construtor com parâmetros
O uso mais importante do construtor é receber os dados do objeto e guardá-los nos atributos. A Ana reescreveu a classe:
class Fruta {
String nome;
double preco;
Fruta(String nomeInicial, double precoInicial) {
nome = nomeInicial;
preco = precoInicial;
}
void mostrar() {
System.out.println(nome + ": R$ " + preco);
}
}
public class Main {
public static void main(String[] args) {
Fruta maca = new Fruta("maçã", 7.9);
Fruta banana = new Fruta("banana", 3.5);
maca.mostrar();
banana.mostrar();
}
}
$ javac Main.java && java Main maçã: R$ 7.9 banana: R$ 3.5
- Fruta(String nomeInicial, double precoInicial)
- Um construtor com dois parâmetros. Agora, para criar uma fruta, é obrigatório informar nome e preço.
- nome = nomeInicial;
- Copia o valor recebido para o atributo
nomedo objeto que está nascendo. - new Fruta("maçã", 7.9)
- Cria o objeto e chama o construtor com os argumentos, na mesma ordem e tipos dos parâmetros. Uma linha, objeto pronto.
Compare com o jeito antigo:
| Sem construtor | Com construtor |
|---|---|
Fruta maca = new Fruta();maca.nome = "maçã";maca.preco = 7.9; | Fruta maca = new Fruta("maçã", 7.9); |
Dá para esquecer uma linha, e a fruta fica com nome null | O compilador não deixa criar uma fruta sem nome e preço |
nomeInicial e não só nome?
Se o parâmetro se chamasse nome, igual ao atributo, a linha nome = nome; não faria o que parece: o parâmetro faria sombra no atributo, e o valor seria copiado dele para ele mesmo. Na prática, os programadores Java usam o mesmo nome e resolvem a sombra com this.nome = nome;. Essa é a próxima aula, this. Por enquanto, nomes diferentes deixam tudo claro.
Missão: escreva um construtor para a classe Conta que receba o titular (String) e o saldo inicial (double). Depois troque, no main, as linhas de atribuição por new Conta("Ana", 500) e new Conta("Bruno", 80).
class Conta {
String titular;
double saldo;
// escreva o construtor aqui
void mostrar() {
System.out.println(titular + ": R$ " + saldo);
}
}
public class Main {
public static void main(String[] args) {
Conta a = new Conta();
a.titular = "Ana";
a.saldo = 500;
Conta b = new Conta();
b.titular = "Bruno";
b.saldo = 80;
a.mostrar();
b.mostrar();
}
}
class Conta {
String titular;
double saldo;
Conta(String titularInicial, double saldoInicial) {
titular = titularInicial;
saldo = saldoInicial;
}
void mostrar() {
System.out.println(titular + ": R$ " + saldo);
}
}
public class Main {
public static void main(String[] args) {
Conta a = new Conta("Ana", 500);
Conta b = new Conta("Bruno", 80);
a.mostrar();
b.mostrar();
}
}
Quando você escreve um construtor, o padrão some
Este é o ponto que mais pega quem está começando. O Java só cria o construtor padrão (vazio) se a classe não tiver nenhum. A partir do momento em que você escreve Fruta(String, double), o Fruta() automático deixa de existir:
class Fruta {
String nome;
double preco;
Fruta(String nomeInicial, double precoInicial) {
nome = nomeInicial;
preco = precoInicial;
}
}
public class Main {
public static void main(String[] args) {
Fruta maca = new Fruta();
}
}
$ javac Main.java
Main.java:13: error: constructor Fruta in class Fruta cannot be applied to given types;
Fruta maca = new Fruta();
^
required: String,double
found: no arguments
reason: actual and formal argument lists differ in length
1 error
| Linha da mensagem | Tradução |
|---|---|
constructor Fruta in class Fruta cannot be applied to given types; | O construtor de Fruta não pode ser usado com os argumentos dados. |
required: String,double | O único construtor que existe pede um texto e um número. |
found: no arguments | A chamada não mandou nada. |
reason: actual and formal argument lists differ in length | As listas têm tamanhos diferentes. |
É a mesma mensagem dos métodos com argumentos errados, só que com constructor no lugar de method. Há duas saídas: passar os argumentos (new Fruta("maçã", 7.9)) ou, se fizer sentido criar frutas "em branco", escrever também um construtor sem parâmetros. Isso leva à próxima seção.
Vários construtores
Uma classe pode ter vários construtores, desde que as listas de parâmetros sejam diferentes. É a mesma regra da sobrecarga de métodos. Assim, quem cria o objeto escolhe quanta informação passar:
class Fruta {
String nome;
double preco;
int estoque;
Fruta(String nomeInicial) {
nome = nomeInicial;
preco = 0.0;
estoque = 0;
}
Fruta(String nomeInicial, double precoInicial) {
nome = nomeInicial;
preco = precoInicial;
estoque = 10;
}
Fruta(String nomeInicial, double precoInicial, int estoqueInicial) {
nome = nomeInicial;
preco = precoInicial;
estoque = estoqueInicial;
}
}
Funciona, mas olhe a repetição: as linhas nome = …, preco = … e estoque = … aparecem três vezes. Se amanhã a Ana precisar validar o preço (não aceitar negativo), terá de lembrar dos três lugares.
this(…): um construtor chama o outro
Dentro de um construtor, this(…) quer dizer "chame outro construtor desta mesma classe, com estes argumentos". Os construtores curtos passam a delegar para o completo, que é o único a fazer o trabalho:
class Fruta {
String nome;
double preco;
int estoque;
Fruta() {
this("sem nome", 0.0, 0);
}
Fruta(String nomeInicial) {
this(nomeInicial, 0.0, 0);
}
Fruta(String nomeInicial, double precoInicial) {
this(nomeInicial, precoInicial, 10);
}
Fruta(String nomeInicial, double precoInicial, int estoqueInicial) {
nome = nomeInicial;
preco = precoInicial;
estoque = estoqueInicial;
System.out.println("Criada: " + nome + ", R$ " + preco + ", estoque " + estoque);
}
}
public class Main {
public static void main(String[] args) {
new Fruta();
new Fruta("kiwi");
new Fruta("maçã", 7.9);
new Fruta("uva", 12.0, 30);
}
}
$ javac Main.java && java Main Criada: sem nome, R$ 0.0, estoque 0 Criada: kiwi, R$ 0.0, estoque 0 Criada: maçã, R$ 7.9, estoque 10 Criada: uva, R$ 12.0, estoque 30
- this("sem nome", 0.0, 0);
- Chama o construtor de três parâmetros com valores "padrão". O Java escolhe qual construtor pelos tipos dos argumentos, como na sobrecarga.
- this(nomeInicial, precoInicial, 10);
- Repassa o que recebeu e completa o que falta (estoque 10).
- o construtor de três parâmetros
- O único que realmente preenche os atributos. Toda fruta, criada por qualquer construtor, passa por ele. Por isso o
printlnaparece nas quatro criações. - new Fruta();
- Um
newsem guardar o objeto numa variável é permitido; aqui só queremos ver o construtor rodar.
É a técnica dos "valores padrão" que você viu em Sobrecarga, aplicada a construtores. Regras do this(…):
- Só pode ser usado dentro de um construtor, e no máximo uma vez.
- Um construtor não pode chamar a si mesmo, nem formar um ciclo (A chama B, que chama A). O
javacrecusa:recursive constructor invocation. - Antes do
this(…), o objeto ainda não está pronto: nenhuma linha antes dele pode usar os atributos ou métodos do objeto.
this(…) precisa ser a primeira linha? Java 25+
Até o Java 24, sim: qualquer instrução antes do this(…) era erro. O Java 25 trouxe os "corpos de construtor flexíveis": agora você pode escrever instruções antes, desde que elas não usem o objeto que está nascendo (por exemplo, validar ou imprimir um parâmetro). Veja:
class Fruta {
String nome;
double preco;
Fruta(String nomeInicial) {
System.out.println("Preparando " + nomeInicial);
this(nomeInicial, 0.0);
}
Fruta(String nomeInicial, double precoInicial) {
nome = nomeInicial;
preco = precoInicial;
System.out.println("Criada: " + nome);
}
}
public class Main {
public static void main(String[] args) {
new Fruta("kiwi");
}
}
Pedindo ao javac que compile como Java 21 (opção --release 21), o código é recusado; no Java 25, compila e roda:
$ javac --release 21 Main.java
Main.java:7: error: flexible constructors is not supported in -source 21
this(nomeInicial, 0.0);
^
(use -source 25 or higher to enable flexible constructors)
1 error
$ javac Main.java && java Main
Preparando kiwi « o println antes do this(…) rodou primeiro
Criada: kiwi
Se o seu código precisa rodar em versões antigas, mantenha o this(…) na primeira linha. E, mesmo no Java 25, ler um atributo antes dele é erro: cannot reference nome before supertype constructor has been called.
Formas diferentes de criar objetos prontos
| Forma | Exemplo | Quando usar |
|---|---|---|
| Construtor padrão + atribuições | new Fruta(); f.nome = …; | Só em exemplos; fácil esquecer um campo |
| Um construtor com todos os dados | new Fruta("maçã", 7.9) | O caso mais comum: todo objeto precisa desses dados |
Vários construtores + this(…) | new Fruta("kiwi") e new Fruta("uva", 12.0, 30) | Alguns dados são opcionais e têm um valor padrão razoável |
| Valor inicial no atributo | int estoque = 10; | O valor é o mesmo para todos e não depende de parâmetro |
| record | record Fruta(String nome, double preco) {} | Classes que só carregam dados imutáveis: o Java escreve o construtor por você |
Quando dá errado
$ javac Main.java
Main.java:13: error: constructor Fruta in class Fruta cannot be applied to given types;
Fruta maca = new Fruta();
^
required: String,double
found: no arguments
reason: actual and formal argument lists differ in length
1 error
O que aconteceu: nenhum construtor da classe aceita os argumentos do new. O caso clássico: a classe ganhou um construtor com parâmetros, o padrão sumiu, e algum lugar ainda faz new Fruta().
Como resolver: passe os argumentos que o construtor pede (veja a linha required) ou acrescente um construtor sem parâmetros, de preferência com this(…).
$ javac Main.java
Main.java:13: error: incompatible types: double cannot be converted to String
Fruta maca = new Fruta(7.9, "maçã");
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
O que aconteceu: o construtor é Fruta(String, double) e a chamada mandou primeiro o número.
Como resolver: new Fruta("maçã", 7.9), na ordem da declaração.
void (virou método!)
class Fruta {
String nome;
double preco;
void Fruta(String nomeInicial, double precoInicial) {
nome = nomeInicial;
preco = precoInicial;
}
}
public class Main {
public static void main(String[] args) {
Fruta maca = new Fruta("maçã", 7.9);
}
}
$ javac Main.java
Main.java:13: error: constructor Fruta in class Fruta cannot be applied to given types;
Fruta maca = new Fruta("maçã", 7.9);
^
required: no arguments
found: String,double
reason: actual and formal argument lists differ in length
1 error
O que aconteceu: com void na frente, Fruta(…) deixou de ser construtor: virou um método comum que, por acaso, tem o nome da classe. Como a classe ficou sem construtor nenhum, o Java criou o padrão, sem parâmetros. Daí o curioso required: no arguments.
Como resolver: apague o void. Construtores nunca têm tipo de retorno.
$ javac Main.java
Main.java:6: error: recursive constructor invocation
this(nomeInicial);
^
1 error
O que aconteceu: o construtor Fruta(String) chamou this(nomeInicial), ou seja, ele mesmo. Seria uma recursão sem fim, e o compilador a detecta antes de rodar.
Como resolver: o this(…) deve chamar um construtor diferente, normalmente o mais completo.
$ java Main.java null: R$ 0.0
O que aconteceu: o construtor foi escrito como Fruta(String nome, double preco) { nome = nome; preco = preco; }. Os parâmetros têm o mesmo nome dos atributos e fazem sombra neles: cada linha copia o parâmetro para ele mesmo. Os atributos nunca foram tocados. Compila, roda, e está errado.
Como resolver: this.nome = nome; e this.preco = preco;. A explicação completa está em this.
Referência
| Escrita | Significado |
|---|---|
NomeDaClasse(params) { … } | Construtor: nome igual ao da classe, sem tipo de retorno |
new NomeDaClasse(args) | Cria o objeto e chama o construtor que combina com os argumentos |
| Construtor padrão | Criado pelo Java só se a classe não tiver nenhum construtor; sem parâmetros, vazio |
| Vários construtores | Permitido, com listas de parâmetros diferentes (sobrecarga) |
this(args); | Dentro de um construtor: chama outro construtor da mesma classe |
void NomeDaClasse(…) | Não é construtor: é um método com nome estranho |
Missão final: dois jeitos de matricular
Missão: a classe Aluno já tem o construtor Aluno(String nomeInicial, String turmaInicial). Escreva um segundo construtor, Aluno(String nomeInicial), que use this(…) para matricular o aluno na turma "1A". O main já cria a Carla (sem turma) e o Davi (turma 2B).
class Aluno {
String nome;
String turma;
Aluno(String nomeInicial, String turmaInicial) {
nome = nomeInicial;
turma = turmaInicial;
}
// escreva o construtor com um parâmetro aqui
void mostrar() {
System.out.println(nome + " - turma " + turma);
}
}
public class Main {
public static void main(String[] args) {
Aluno carla = new Aluno("Carla");
Aluno davi = new Aluno("Davi", "2B");
carla.mostrar();
davi.mostrar();
}
}
class Aluno {
String nome;
String turma;
Aluno(String nomeInicial, String turmaInicial) {
nome = nomeInicial;
turma = turmaInicial;
}
Aluno(String nomeInicial) {
this(nomeInicial, "1A");
}
void mostrar() {
System.out.println(nome + " - turma " + turma);
}
}
public class Main {
public static void main(String[] args) {
Aluno carla = new Aluno("Carla");
Aluno davi = new Aluno("Davi", "2B");
carla.mostrar();
davi.mostrar();
}
}
Exercícios
1. O nome do construtor
Complete a declaração do construtor da classe Livro que recebe o título:
2. Criar com argumentos
O construtor é Conta(String titular, double saldo). Complete para criar a conta da Ana com 300:
3. Encadear
Complete o construtor sem parâmetros para chamar o construtor Fruta(String) com "sem nome":
4. A classe Fruta só tem o construtor Fruta(String nome). O que acontece com new Fruta()?
5. Qual é a diferença de declaração entre um construtor e um método?
6. Quantas vezes o construtor roda para cada objeto?
Resumo
- O construtor roda uma vez, no
new, para deixar o objeto pronto. Tem o nome da classe e nenhum tipo de retorno. - Sem construtor escrito, o Java cria o padrão (vazio, sem parâmetros). Escreveu algum? O padrão some.
- Construtores com parâmetros obrigam quem cria o objeto a informar os dados:
new Fruta("maçã", 7.9). - Vários construtores = sobrecarga. Use
this(…)para os curtos chamarem o completo. - Erros típicos:
constructor … cannot be applied to given types(argumentos não combinam),voidno construtor (vira método),recursive constructor invocation. - Com parâmetros de mesmo nome dos atributos, use
this.nome = nome(próxima aula).