Solucionado (ver solução)
Solucionado
(ver solução)
8
respostas

Método sobrescrito

Vou colocar aqui as minhas classes e, ao final, faço a pergunta.

public class Conta {

    protected double saldo;
    protected String nome;

    public double getSaldo() {
        return this.saldo;
    }

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

    public void saca(double valor) throws Exception {
        if(this.saldo >= valor) {
            this.saldo -= valor;
        } else {
            throw new Exception();
        }
    }

    public void atualiza(double taxa) {
        this.saldo += this.saldo * taxa;
    }
}
public class ContaCorrente extends Conta {

    protected String apelido;

    public void atualiza(double taxa) {
        this.saldo += this.saldo * (taxa * 2);
    }

    @Override
    public void saca(double valor) {
        this.saldo -= valor;
    }
}
public class main {

    public static void main(String[] args) {

        Conta cc = new ContaCorrente();

        cc.deposita(1000);
        System.out.println("Saldo: " + cc.getSaldo());

        try {
            cc.saca(1400);
        } catch (Exception e) {
            System.out.println("Tem dinheiro não, bródi!");
        }

        System.out.println("Saldo: " + cc.getSaldo());
    }
}

Por que o método saca() que está sendo executado é o da classe ContaCorrente (sem validação) e não o da classe Conta? Até o Eclipse, quando passo o mouse em cima do nome do método, diz que ele é da Conta.

8 respostas

Olá Marcelo, por que foi sobrescrito, isso quer dizer que o Java irá procurar na ordem no caso, dos filhos pro pai, caso sua classe ContaCorrente não tivesse sobrescrito esse método, e é indicado com o @Override o método chamado seria o da classe Conta porque está indicado que a classe ContaCorrente extends Conta.

Espero ter ajudado.

Certo. Certo. Tudo isso faz sentido. Só que, quando eu escrevo o nome da variável (cc) e coloco o ponto, o Eclipse apresenta como opções somente os métodos da classe pai (Conta), já que na criação da variável eu disse que ela seria do tipo Conta mas receberia new ContaCorrente() Então concluí que o método usado era o da classe pai e não o da filha...

solução!

Olá Marcelo, tudo bem? Em tempo de compilação vai ser verificado o tipo da referência, nesse caso, como você mesmo disse, como o tipo é Conta, apenas os métodos definidos nessa classe Conta poderão ser chamados, isso é feito em tempo de compilação. Porém, em tempo de execução, você está atribuindo um objeto filho de Conta para a referência cc, ou seja, um objeto do tipo ContaCorrente está sendo referenciado pela referência cc (lembrando: isso é em tempo de execução!). Ou seja, em tempo de compilação (e o Eclipse também só valida tempo de compilação) será possível chamar apenas os métodos em Conta, porém através do polimorfismo e da sobrescrita, em tempo de execução, o método que de fato será chamado é o método saca() de ContaCorrente.

Marcelo, resumindo ao que o Otávio disse. Quando você escreve uma herança o IDE ainda não sabe sobre qual classe ele irá executar. Por exemplo você poderia ter vários tipos de conta, no entanto somente será possível definir o que será executado em tempo de execução, assim contando que primeiro é executado os métodos da classe filho e depois da classe pai.

Isso tem haver com um conceito chamado dynamic binding. Resumidamente, o compilador não é capaz de determinar, em tempo de compilação, qual o método deve ser utilizado, pois dependerá de qual o tipo do objeto que está realizando a chamada.

Se você quisesse usar o método da superclasse dentro da classe teria de usar super.saca(valor), de outro modo, o método redefinido será chamado.

A explicação de como isso funciona é um pouco complexa, e exige conceitos bastante avançados. Segue um link com uma explicação mais detalhada.

http://wpollock.com/Java/PolyMorphism.htm

Para um entendimento completo, entretanto, recomendo a leitura do PDF chamado Object Oriented Programming in ANSI C, segue link abaixo. O mesmo explica de que modo é possível implementar todas as funcionalidades de linguagens orientadas a objetos em C, e dá um poderoso insight sobre como a implementação dessas funcionalidades é de fato realizada pelos compiladores em linguagens orientadas a objetos.

https://www.cs.rit.edu/~ats/books/ooc.pdf

Ótimas explicações, Senhores! Só pra fechar este "chamado", pergunto: as boas práticas indicam o que? Transformar a classe Conta em abstrata e colocar as validações nas classes filhas dela? Ou o melhor mesmo seria colocar essas validações em uma interface?

Eu, particularmente, defini Conta como uma classe abstrata, pois, ainda que tenha um método abstrato, possui muito código útil para reuso. Como esses aspectos básicos dificilmente mudariam no futuro, julguei a herança mais útil. Ao optar por uma interface, teria de duplicar esse código em todas as classes que a implementassem. O que, a meu ver, neste caso, não seria interessante.

De forma geral, entretanto, o abuso no uso de herança pode tornar a arquitetura de software muito engessada, dificultando a manutenção. Por este motivo é comum que se incentive o uso de interfaces em metodologias ágeis, pois dificilmente se sabe, de início, a totalidade dos requisitos. Uma decisão equivocada pode ser muito difícil de concertar, devendo-se muitas vezes sacrificar a conveniência momentânea por flexibilidade a longo prazo.

Certíssimo!