Java this

this ("este", em inglês) é uma palavra do Java que significa "o objeto em que este código está rodando agora". Ela aparece o tempo todo em código profissional, quase sempre na forma this.nome = nome;. Nesta aula você entende por que ela existe, o bug clássico de quem a esquece e os outros usos dela.

Você vai aprender
  • O bug clássico: nome = nome; no construtor, que compila e não faz nada.
  • Como this.nome diferencia o atributo do parâmetro de mesmo nome.
  • Que this é uma referência de verdade ao objeto atual: dá para imprimir, comparar, passar e devolver.
  • O this(…), que chama outro construtor.
  • Por que não existe this em métodos static.

A ideia antes do código: "eu"

Quando a Ana diz "meu nome é Ana", a palavra "meu" se refere a ela. Quando o Bruno diz "meu nome é Bruno", a mesma palavra se refere a ele. "Meu" não é o nome de ninguém: é um jeito de cada um apontar para si mesmo.

O this é o "eu" de cada objeto. O código de um método é escrito uma vez, na classe, mas roda em objetos diferentes. Dentro dele, this aponta para o objeto que recebeu a chamada: em maca.mostrar(), this é a maçã; em uva.mostrar(), é a uva.


O bug clássico: nome = nome

Na aula de Construtores, os parâmetros tinham nomes como nomeInicial. Isso é chato: o nome bom, nome, já está ocupado pelo atributo. A Ana resolveu usar o mesmo nome nos dois:

class Fruta {
    String nome;
    double preco;

    Fruta(String nome, double preco) {
        nome = nome;
        preco = preco;
    }
}

public class Main {
    public static void main(String[] args) {
        Fruta maca = new Fruta("maçã", 7.9);
        System.out.println(maca.nome + ": R$ " + maca.preco);
    }
}
$ javac Main.java && java Main
null: R$ 0.0          « a maçã ficou sem nome e sem preço!

Compila sem nenhum aviso (nem com javac -Xlint:all), roda sem erro e o resultado está errado. O que aconteceu? Lembre do sombreamento: dentro do construtor existem dois nome, o atributo do objeto e o parâmetro. O mais "próximo", o parâmetro, faz sombra no atributo. Então a linha nome = nome; quer dizer "parâmetro nome recebe o parâmetro nome": copia o valor para ele mesmo. O atributo nunca é tocado e fica com o valor padrão, null.

Dentro do construtorQuem éValor
nomeO parâmetro (faz sombra no atributo)"maçã"
this.nomeO atributo do objeto que está nascendonull, até alguém atribuir

A correção: this.nome = nome

class Fruta {
    String nome;
    double preco;

    Fruta(String nome, double preco) {
        this.nome = nome;
        this.preco = preco;
    }

    void mudarPreco(double preco) {
        this.preco = preco;
    }
}

public class Main {
    public static void main(String[] args) {
        Fruta maca = new Fruta("maçã", 7.9);
        System.out.println(maca.nome + ": R$ " + maca.preco);
        maca.mudarPreco(8.5);
        System.out.println(maca.nome + ": R$ " + maca.preco);
    }
}
$ javac Main.java && java Main
maçã: R$ 7.9
maçã: R$ 8.5
this
O objeto atual: o que está sendo criado (no construtor) ou o que recebeu a chamada (num método).
.nome
O atributo nome desse objeto. this.nome é como maca.nome, só que de dentro da classe.
= nome;
Sem this, nome é o parâmetro. A linha lê-se: "o meu nome recebe o nome que eu recebi".
mudarPreco(double preco)
O mesmo padrão vale para métodos comuns: sempre que um parâmetro tiver o nome de um atributo, use this. para chegar ao atributo.

Esse é o padrão de praticamente todo código Java: parâmetros com o mesmo nome dos atributos, e this.atributo = parametro;. As IDEs geram construtores assim automaticamente.

Missão: o construtor da Conta tem o bug clássico e a conta da Ana sai como null: R$ 0.0. Corrija as duas linhas com this para aparecer Ana: R$ 500.0.

class Conta {
    String titular;
    double saldo;

    Conta(String titular, double saldo) {
        titular = titular;
        saldo = saldo;
    }
}

public class Main {
    public static void main(String[] args) {
        Conta c = new Conta("Ana", 500);
        System.out.println(c.titular + ": R$ " + c.saldo);
    }
}
class Conta {
    String titular;
    double saldo;

    Conta(String titular, double saldo) {
        this.titular = titular;
        this.saldo = saldo;
    }
}

public class Main {
    public static void main(String[] args) {
        Conta c = new Conta("Ana", 500);
        System.out.println(c.titular + ": R$ " + c.saldo);
    }
}

this é uma referência de verdade

this não é só um prefixo para atributos: é uma referência ao objeto atual, igual às variáveis que você viu em Classes e Objetos. Dá para imprimi-la e compará-la:

class Fruta {
    String nome;
    double preco;

    Fruta(String nome, double preco) {
        this.nome = nome;
        this.preco = preco;
    }

    void quemSouEu() {
        System.out.println("this = " + this);
    }

    boolean maisCaraQue(Fruta outra) {
        return this.preco > outra.preco;
    }
}

public class Main {
    public static void main(String[] args) {
        Fruta maca = new Fruta("maçã", 7.9);
        Fruta uva = new Fruta("uva", 12.0);
        System.out.println("maca = " + maca);
        maca.quemSouEu();
        System.out.println("uva = " + uva);
        uva.quemSouEu();
        System.out.println("uva mais cara que maçã? " + uva.maisCaraQue(maca));
        System.out.println("maçã mais cara que uva? " + maca.maisCaraQue(uva));
    }
}
$ javac Main.java && java Main
maca = Fruta@1dbd16a6
this = Fruta@1dbd16a6        « dentro de maca.quemSouEu(), this É a maçã
uva = Fruta@251a69d7
this = Fruta@251a69d7        « dentro de uva.quemSouEu(), this é a uva
uva mais cara que maçã? true
maçã mais cara que uva? false

O identificador depois do @ é o mesmo para maca e para o this da chamada maca.quemSouEu(): são o mesmo objeto. No maisCaraQue, o this.preco não era obrigatório (não há sombra), mas deixa a comparação mais legível: "o meu preço é maior que o preço da outra?".

Passar this para outro objeto

Um objeto pode se "apresentar" a outro passando this como argumento. Na escola da Ana, o aluno se matricula numa turma, e a turma precisa conhecer o aluno:

class Aluno {
    String nome;
    Turma turma;

    Aluno(String nome) {
        this.nome = nome;
    }

    void matricular(Turma turma) {
        this.turma = turma;
        turma.receber(this);
    }
}

class Turma {
    String codigo;
    int quantidade;

    Turma(String codigo) {
        this.codigo = codigo;
    }

    void receber(Aluno aluno) {
        quantidade++;
        System.out.println(aluno.nome + " entrou na turma " + codigo + " (" + quantidade + " aluno(s))");
    }
}

public class Main {
    public static void main(String[] args) {
        Turma t = new Turma("1A");
        new Aluno("Bruno").matricular(t);
        new Aluno("Carla").matricular(t);
    }
}
$ javac Main.java && java Main
Bruno entrou na turma 1A (1 aluno(s))
Carla entrou na turma 1A (2 aluno(s))

Em matricular, this.turma = turma; guarda a turma no aluno, e turma.receber(this); entrega à turma a referência do próprio aluno ("sou eu que estou entrando"). Repare também em new Aluno("Bruno").matricular(t): dá para chamar um método direto no objeto recém-criado.

Devolver this: chamadas em cadeia

Se um método devolve this, quem chamou recebe de volta o mesmo objeto e pode chamar outro método em seguida, na mesma linha:

class Pedido {
    String itens = "";
    double total = 0;

    Pedido adicionar(String fruta, double preco) {
        this.itens = this.itens + fruta + " ";
        this.total = this.total + preco;
        return this;
    }

    void fechar() {
        System.out.println("Itens: " + itens);
        System.out.println("Total: R$ " + total);
    }
}

public class Main {
    public static void main(String[] args) {
        Pedido pedido = new Pedido();
        pedido.adicionar("maçã", 7.9).adicionar("uva", 12.0).adicionar("kiwi", 2.5);
        pedido.fechar();
    }
}
$ javac Main.java && java Main
Itens: maçã uva kiwi
Total: R$ 22.4

O tipo de retorno de adicionar é a própria classe, Pedido. pedido.adicionar("maçã", 7.9) devolve o pedido; nele, chama-se .adicionar("uva", 12.0), que devolve o pedido de novo, e assim por diante. Esse estilo se chama interface fluente e você vai encontrá-lo no StringBuilder (sb.append("a").append("b")) e nos Streams.


this(…) no construtor

Com parênteses logo depois, this(…) não é o objeto: é a chamada de outro construtor da mesma classe. Você já viu em Construtores. Junto com o this.campo, fica assim:

class Fruta {
    String nome;
    double preco;

    Fruta(String nome) {
        this(nome, 0.0);           // chama o construtor de baixo
    }

    Fruta(String nome, double preco) {
        this.nome = nome;          // atributo = parâmetro
        this.preco = preco;
    }
}
FormaO que éOnde pode aparecer
this.nomeAtributo do objeto atualConstrutores e métodos de instância
this.metodo()Método do objeto atual (o this. é opcional)Construtores e métodos de instância
this sozinhoReferência ao objeto atual (imprimir, comparar, passar, devolver)Construtores e métodos de instância
this(…)Chamada a outro construtor da mesma classeSó dentro de construtores

Quando usar this.?

Situaçãothis. é…
Parâmetro ou variável local com o mesmo nome do atributoObrigatório para chegar ao atributo
Sem sombra (nomes diferentes)Opcional: preco e this.preco são a mesma coisa
Comparar com outro objeto do mesmo tipo (this.preco > outra.preco)Opcional, mas deixa a leitura mais clara
Métodos staticProibido: não há objeto atual

Algumas equipes escrevem this. sempre, para deixar claro o que é atributo; outras só quando é obrigatório. As duas formas estão certas. O importante é nunca esquecer nos construtores e setters (veja Encapsulamento).


Quando dá errado

Os atributos ficaram null / 0 depois do construtor
$ java Main.java
null: R$ 0.0

O que aconteceu: o bug clássico: nome = nome; com parâmetro e atributo de mesmo nome. O parâmetro faz sombra, e a linha copia o parâmetro para ele mesmo. Nenhuma mensagem de erro ou aviso.

Como resolver: this.nome = nome;. Sempre que vir x = x;, desconfie.

"non-static variable this cannot be referenced from a static context"
public class Main {
    String saudacao = "Olá";

    public static void main(String[] args) {
        System.out.println(this.saudacao);
    }
}
$ javac Main.java
Main.java:5: error: non-static variable this cannot be referenced from a static context
        System.out.println(this.saudacao);
                           ^
1 error

O que aconteceu: o main é static: roda sem nenhum objeto. Não existe "objeto atual", logo não existe this.

Como resolver: crie um objeto e use a variável dele (Main m = new Main(); m.saudacao), ou torne o atributo static se ele for da classe. Veja static.

"recursive constructor invocation" ou "constructor … cannot be applied"

O que aconteceu: o this(…) chamou o próprio construtor, ou passou argumentos que nenhum construtor aceita.

Como resolver: veja os cenários em Construtores.


Referência

EscritaSignificado
this.campo = campo;Atributo recebe o parâmetro de mesmo nome (o uso mais comum)
thisReferência ao objeto atual
outro.receber(this);Passa o objeto atual para outro objeto
return this;Devolve o objeto atual (chamadas em cadeia)
this(args);Chama outro construtor da mesma classe
this em staticErro: non-static variable this cannot be referenced from a static context

Missão final: a conta fluente

Missão: faça depositar e sacar devolverem a própria conta (tipo de retorno Conta e return this;) para que a linha em cadeia do main funcione e o programa mostre Saldo: 120.0.

class Conta {
    double saldo;

    void depositar(double valor) {
        this.saldo = this.saldo + valor;
    }

    void sacar(double valor) {
        this.saldo = this.saldo - valor;
    }
}

public class Main {
    public static void main(String[] args) {
        Conta c = new Conta();
        c.depositar(100).depositar(50).sacar(30);
        System.out.println("Saldo: " + c.saldo);
    }
}
class Conta {
    double saldo;

    Conta depositar(double valor) {
        this.saldo = this.saldo + valor;
        return this;
    }

    Conta sacar(double valor) {
        this.saldo = this.saldo - valor;
        return this;
    }
}

public class Main {
    public static void main(String[] args) {
        Conta c = new Conta();
        c.depositar(100).depositar(50).sacar(30);
        System.out.println("Saldo: " + c.saldo);
    }
}

Exercícios

1. Corrigir o construtor

Complete a linha do construtor Aluno(String nome) que guarda o parâmetro no atributo nome:

2. Devolver o objeto

Complete a última linha de um método que permite chamadas em cadeia:

return ;

3. Chamar outro construtor

Complete o construtor Fruta() para chamar Fruta(String nome, double preco) com "sem nome" e 0.0:

Fruta() { }

4. No construtor Conta(double saldo) { saldo = saldo; }, o que acontece com o atributo saldo?

5. Em uva.mostrar(), dentro de mostrar, o que é this?

6. Onde this não pode ser usado?


Resumo

  • this é a referência ao objeto atual: o que está sendo construído ou o que recebeu a chamada.
  • Parâmetro com o mesmo nome do atributo faz sombra; nome = nome; não muda nada. Use this.nome = nome;.
  • this pode ser impresso, comparado, passado como argumento (turma.receber(this)) e devolvido (return this;, para chamadas em cadeia).
  • this(…) chama outro construtor da mesma classe.
  • Em métodos static não há objeto atual: this não existe ali.