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).

Você vai aprender
  • 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 com void e o recursive 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, nem int, nem nada antes do nome. Um construtor não devolve valor; quem "devolve" o objeto pronto é o new. É 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 nome do 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 construtorCom 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 nullO compilador não deixa criar uma fruta sem nome e preço
Por que 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 mensagemTraduçã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,doubleO único construtor que existe pede um texto e um número.
found: no argumentsA chamada não mandou nada.
reason: actual and formal argument lists differ in lengthAs 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 println aparece nas quatro criações.
new Fruta();
Um new sem 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 javac recusa: 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.
O 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

FormaExemploQuando usar
Construtor padrão + atribuiçõesnew Fruta(); f.nome = …;Só em exemplos; fácil esquecer um campo
Um construtor com todos os dadosnew 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 atributoint estoque = 10;O valor é o mesmo para todos e não depende de parâmetro
recordrecord Fruta(String nome, double preco) {}Classes que só carregam dados imutáveis: o Java escreve o construtor por você

Quando dá errado

"constructor … cannot be applied to given types"
$ 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(…).

Argumentos na ordem errada
$ 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.

Construtor com 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.

"recursive constructor invocation"
$ 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.

O construtor rodou, mas os atributos ficaram vazios
$ 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

EscritaSignificado
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ãoCriado pelo Java só se a classe não tiver nenhum construtor; sem parâmetros, vazio
Vários construtoresPermitido, 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:

(String tituloInicial) {

2. Criar com argumentos

O construtor é Conta(String titular, double saldo). Complete para criar a conta da Ana com 300:

Conta c =

3. Encadear

Complete o construtor sem parâmetros para chamar o construtor Fruta(String) com "sem nome":

Fruta() { }

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), void no construtor (vira método), recursive constructor invocation.
  • Com parâmetros de mesmo nome dos atributos, use this.nome = nome (próxima aula).