Java Métodos de Objeto

Na aula de Atributos, o main mexia direto no saldo de cada conta: daAna.saldo = daAna.saldo + 500. Funciona, mas a lógica fica espalhada pelo programa. O jeito orientado a objetos é dar à própria conta o comportamento de depositar: daAna.depositar(500). Esses são os métodos de objeto (ou métodos de instância), os métodos sem static.

Você vai aprender
  • Como escrever um método de instância e por que ele não leva static.
  • Como ele usa os atributos do próprio objeto, sem ponto.
  • Como chamar com objeto.metodo() e como o Java sabe "de qual objeto" é cada atributo.
  • Métodos que devolvem valores, que chamam outros métodos do mesmo objeto e que recebem outros objetos.
  • A diferença entre métodos static e de instância, e o erro non-static method … cannot be referenced from a static context.

A ideia antes do código: pedir em vez de fazer

No banco, você não entra no cofre para pôr dinheiro na sua conta: você pede ao caixa "deposite 500 na conta da Ana". Quem sabe como fazer um depósito é o banco. Em POO, é igual: em vez de o main mexer nos dados de cada objeto, ele pede ao objeto que faça algo, e o objeto sabe como mexer nos próprios dados.

Mexendo direto (aula anterior)Pedindo ao objeto (esta aula)
daAna.saldo = daAna.saldo + 500;daAna.depositar(500);
Cada lugar do programa repete a contaA regra do depósito está escrita uma vez, dentro da classe
Fácil errar (e se alguém escrever - em vez de +?)Quem chama só diz o que quer, não como

Seu primeiro método de objeto

class Conta {
    String titular;
    double saldo;

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

    void mostrarSaldo() {
        System.out.println(titular + " tem R$ " + saldo);
    }
}

public class Main {
    public static void main(String[] args) {
        Conta daAna = new Conta();
        daAna.titular = "Ana";
        Conta doBruno = new Conta();
        doBruno.titular = "Bruno";

        daAna.depositar(500);
        doBruno.depositar(80);
        daAna.depositar(20);

        daAna.mostrarSaldo();
        doBruno.mostrarSaldo();
    }
}
$ javac Main.java && java Main
Ana tem R$ 520.0
Bruno tem R$ 80.0
void depositar(double valor)
Um método como os que você já conhece, com parâmetro e tipo de retorno, mas sem static. Isso faz dele um método de instância: ele pertence a cada objeto, e não à classe.
saldo = saldo + valor;
Usa o atributo saldo direto pelo nome, sem ponto. Qual saldo? O do objeto que recebeu o pedido.
void mostrarSaldo()
Outro método de instância, que lê dois atributos (titular e saldo) do próprio objeto.
daAna.depositar(500);
A chamada: objeto, ponto, método e argumentos. Lê-se "conta da Ana, deposite 500".

Como o método sabe de qual objeto é o saldo?

O mesmo código, saldo = saldo + valor;, mexeu no saldo da Ana numa chamada e no do Bruno na outra. O segredo está no que vem antes do ponto. Quando você escreve daAna.depositar(500), o Java executa o método "dentro" do objeto daAna: todo atributo citado no corpo é o atributo desse objeto.

ChamadaDentro do método, saldo é…Saldo depois
daAna.depositar(500)o saldo da AnaAna: 500.0
doBruno.depositar(80)o saldo do BrunoBruno: 80.0
daAna.depositar(20)o saldo da Ana de novoAna: 520.0

Por baixo dos panos, todo método de instância recebe uma referência escondida para o objeto que foi chamado. Ela tem nome: this ("este"). saldo dentro do método é, na verdade, this.saldo. A aula this mostra quando é preciso escrever isso por extenso.

Missão: acrescente à classe Conta um método void sacar(double valor) que tire o valor do saldo. No main, faça a Ana sacar 120 antes do mostrarSaldo(), para aparecer Ana tem R$ 400.0.

class Conta {
    String titular;
    double saldo;

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

    void mostrarSaldo() {
        System.out.println(titular + " tem R$ " + saldo);
    }
}

public class Main {
    public static void main(String[] args) {
        Conta daAna = new Conta();
        daAna.titular = "Ana";
        daAna.depositar(520);

        daAna.mostrarSaldo();
    }
}
class Conta {
    String titular;
    double saldo;

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

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

    void mostrarSaldo() {
        System.out.println(titular + " tem R$ " + saldo);
    }
}

public class Main {
    public static void main(String[] args) {
        Conta daAna = new Conta();
        daAna.titular = "Ana";
        daAna.depositar(520);
        daAna.sacar(120);
        daAna.mostrarSaldo();
    }
}

Métodos que devolvem, decidem e conversam

Tudo o que você aprendeu sobre métodos vale aqui: eles podem devolver valores, ter if, chamar outros métodos. Veja uma conta mais completa:

class Conta {
    String titular;
    double saldo;

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

    boolean sacar(double valor) {
        if (valor > saldo) {
            System.out.println("Saque de " + valor + " negado: saldo insuficiente.");
            return false;
        }
        saldo = saldo - valor;
        return true;
    }

    void transferir(Conta destino, double valor) {
        if (sacar(valor)) {
            destino.depositar(valor);
        }
    }

    String resumo() {
        return titular + ": R$ " + saldo;
    }
}

public class Main {
    public static void main(String[] args) {
        Conta daAna = new Conta();
        daAna.titular = "Ana";
        daAna.depositar(500);
        Conta doBruno = new Conta();
        doBruno.titular = "Bruno";

        boolean deuCerto = daAna.sacar(120);
        System.out.println("Primeiro saque: " + deuCerto);
        System.out.println("Segundo saque: " + daAna.sacar(1000));

        daAna.transferir(doBruno, 50);
        System.out.println(daAna.resumo());
        System.out.println(doBruno.resumo());
    }
}
$ javac Main.java && java Main
Primeiro saque: true
Saque de 1000.0 negado: saldo insuficiente.
Segundo saque: false
Ana: R$ 330.0       « 500 - 120 - 50
Bruno: R$ 50.0      « recebeu a transferência
boolean sacar(double valor)
Devolve true se o saque deu certo e false se não havia saldo. A regra ("não pode sacar mais do que tem") agora mora na classe: nenhum lugar do programa consegue esquecer de conferir.
if (sacar(valor))
Dentro de transferir, a chamada sacar(valor) não tem objeto antes: é o mesmo objeto que está transferindo. Em daAna.transferir(…), quem saca é a Ana.
Conta destino
Um parâmetro cujo tipo é a própria classe. O método recebe a referência de outra conta…
destino.depositar(valor);
…e pede a ela que deposite. Aqui o ponto é necessário: é outro objeto.
String resumo()
Em vez de imprimir, devolve um texto pronto; quem chama decide o que fazer com ele.
Dentro de um método de instância, você escreve……e isso significa
saldoo atributo do objeto que foi chamado
sacar(10)chamar outro método do mesmo objeto
destino.saldo, destino.depositar(10)o atributo ou método de outro objeto

static × de instância

Agora você conhece os dois tipos de método. A diferença é a pergunta "precisa de um objeto?":

Método static (da classe)Método de instância (do objeto)
Declaraçãostatic double media(double a, double b)double media()
ChamadaMain.media(7, 8) ou só media(7, 8) dentro da classealuno.media(): precisa de um objeto antes do ponto
Usa atributos de instância?Não pode: não há "objeto atual"Sim, direto pelo nome
De onde vêm os dados?Só dos parâmetrosDos atributos do objeto (e dos parâmetros)
Exemplos do JavaMath.sqrt(2), Math.max(3, 7), Integer.parseInt("42")texto.length(), texto.toUpperCase(), teclado.nextInt()

Repare nos exemplos: Math.sqrt(2) não precisa de "um Math"; a raiz de 2 é a mesma para todo mundo. Já texto.length() depende de qual texto. Use essa pergunta para decidir: o resultado depende dos dados de um objeto? Então é de instância. Na aula static você aprofunda o assunto.

E o main?

O main é static: o Java o chama antes de existir qualquer objeto. É por isso que, nas aulas de Métodos, tudo precisava de static. A partir de agora, o main vira o lugar que cria os objetos e pede coisas a eles; a lógica fica nos métodos de instância das classes.


Quando dá errado

"non-static method … cannot be referenced from a static context"
class Conta {
    String titular;
    double saldo;

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

public class Main {
    public static void main(String[] args) {
        Conta.depositar(100);
    }
}
$ javac Main.java
Main.java:12: error: non-static method depositar(double) cannot be referenced from a static context
        Conta.depositar(100);
             ^
1 error

O que aconteceu: "o método não estático depositar(double) não pode ser usado a partir de um contexto estático". Conta.depositar(100) pede um depósito à classe (o molde), e não a uma conta. Depositar em qual conta? Não há objeto para receber o dinheiro.

Como resolver: crie (ou use) um objeto e chame por ele: Conta c = new Conta(); c.depositar(100);. O mesmo erro aparece quando você escreve um método sem static na classe Main e o chama do main (veja Métodos).

"cannot find symbol … location: class Main" (chamou sem objeto)
$ javac Main.java
Main.java:12: error: cannot find symbol
        depositar(100);
        ^
  symbol:   method depositar(int)
  location: class Main
1 error

O que aconteceu: sem nada antes, depositar(100) procura um método depositar na classe atual, Main. Ele existe, mas na classe Conta.

Como resolver: minhaConta.depositar(100);, com o objeto antes do ponto.

"cannot find symbol … location: variable conta of type Conta"
$ javac Main.java
Main.java:13: error: cannot find symbol
        conta.sacar(100);
             ^
  symbol:   method sacar(int)
  location: variable conta of type Conta
1 error

O que aconteceu: o objeto está certo, mas a classe Conta não tem um método sacar que aceite um int. Ou o método não foi escrito, ou o nome está diferente.

Como resolver: confira o nome e os parâmetros na classe. Se o método não existe, escreva-o.

"non-static variable … cannot be referenced from a static context" dentro da classe
$ javac Main.java
Main.java:6: error: non-static variable saldo cannot be referenced from a static context
        saldo = saldo + valor;
        ^
Main.java:6: error: non-static variable saldo cannot be referenced from a static context
        saldo = saldo + valor;
                ^
2 errors

O que aconteceu: o método foi declarado como static void depositar(…) (o hábito das primeiras aulas!). Um método static não tem objeto atual, então não sabe de quem é o saldo.

Como resolver: tire o static dos métodos que usam atributos do objeto.

NullPointerException: Cannot invoke …
$ javac Main.java && java Main
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Conta.depositar(double)" because "<local1>" is null
	at Main.main(Main.java:13)

O que aconteceu: "não é possível chamar Conta.depositar(double) porque a variável é nula". A variável conta valia null: não havia objeto para receber o pedido.

Como resolver: garanta um new Conta() antes de chamar métodos. Veja null.


Referência

EscritaSignificado
Tipo nome(params) { … } (sem static, na classe)Método de instância
obj.metodo(args)Chama o método no objeto obj
atributo dentro do métodoO atributo do objeto que foi chamado (o mesmo que this.atributo)
outroMetodo() dentro do métodoChama outro método do mesmo objeto
void f(Classe outro)Recebe a referência de outro objeto; use outro.algo
Classe.metodo()Só para métodos static

Missão final: o aluno que sabe a própria situação

Missão: na classe Aluno, escreva dois métodos de instância: double media(), que devolve a média de nota1 e nota2, e String situacao(), que devolve "aprovado" se a media() for 7 ou mais e "recuperação" se não. O main já está pronto.

class Aluno {
    String nome;
    double nota1;
    double nota2;

    // escreva media() e situacao() aqui
}

public class Main {
    public static void main(String[] args) {
        Aluno bruno = new Aluno();
        bruno.nome = "Bruno";
        bruno.nota1 = 8.0;
        bruno.nota2 = 7.0;

        Aluno davi = new Aluno();
        davi.nome = "Davi";
        davi.nota1 = 5.0;
        davi.nota2 = 6.0;

        System.out.println(bruno.nome + ": " + bruno.media() + " (" + bruno.situacao() + ")");
        System.out.println(davi.nome + ": " + davi.media() + " (" + davi.situacao() + ")");
    }
}
class Aluno {
    String nome;
    double nota1;
    double nota2;

    double media() {
        return (nota1 + nota2) / 2;
    }

    String situacao() {
        if (media() >= 7) {
            return "aprovado";
        }
        return "recuperação";
    }
}

public class Main {
    public static void main(String[] args) {
        Aluno bruno = new Aluno();
        bruno.nome = "Bruno";
        bruno.nota1 = 8.0;
        bruno.nota2 = 7.0;

        Aluno davi = new Aluno();
        davi.nome = "Davi";
        davi.nota1 = 5.0;
        davi.nota2 = 6.0;

        System.out.println(bruno.nome + ": " + bruno.media() + " (" + bruno.situacao() + ")");
        System.out.println(davi.nome + ": " + davi.media() + " (" + davi.situacao() + ")");
    }
}

Exercícios

1. Chamar no objeto

Complete para pedir à conta poupanca que deposite 200:

2. A palavra que sai

Um método que usa atributos do objeto não pode ter qual palavra na declaração?

3. Usar o atributo

Complete o corpo do método da classe Fruta (que tem o atributo double preco) para dar um desconto de 10%:

void darDesconto() { }

4. Em daAna.transferir(doBruno, 50), dentro do método, a chamada sacar(50) (sem nada antes) saca de qual conta?

5. Qual destes faz mais sentido como método static?

6. O que significa non-static method depositar(double) cannot be referenced from a static context em Conta.depositar(100)?


Resumo

  • Métodos de instância são declarados sem static e pertencem a cada objeto.
  • Chamada: objeto.metodo(argumentos). O objeto antes do ponto é o "objeto atual" durante a execução.
  • Dentro do método, atributos e outros métodos do mesmo objeto são usados direto pelo nome; os de outro objeto, com outro.algo.
  • Coloque as regras (como "não sacar mais do que o saldo") dentro dos métodos da classe; o main só pede.
  • static = não depende de objeto (Math.sqrt); de instância = depende dos dados do objeto (texto.length()).
  • Chamar um método de instância pela classe ou de um contexto static sem objeto: non-static method … cannot be referenced from a static context.