Dúvida sobre Classe Abstrata

Estava estudando sobre classes abstratas no Java e me surgiu uma dúvida na hora de instanciar. Por exemplo:

Se eu tenho uma classe abstrata “Conta” e desejo instanciá-la, eu sei que não poderei fazer:

Conta c1 = new Conta();

Porém, eu tenho a subclasse ContaCorrente, a qual o compilador me permite instanciar da seguinte forma:

Conta c1 = new ContaCorrente();

Nesse caso o código compila. Porém, eu gostaria de saber: existe algum erro conceitual nisso? Se não existir, qual é a diferença entre instanciar dessa segunda forma e desta nova (como indicado na apostila):

ContaCorrente c1 = new ContaCorrente();

Agradeço desde já.

Não está errado, este é o conceito do polimorfismo da orientação a objetos. Provavelmente a classe Conta é abstrata pois na modelagem não faz sentido existir um objeto do tipo Conta, sendo suas subclasses responsáveis pela especialização dos métodos de Conta.

Imaginando duas subclasses da classe Conta, ContaCorrente e ContaPoupanca por exemplo:

Conta c1 = new ContaCorrente();
Conta c2 = new ContaPoupanca();

Você pode receber qualquer instância de uma subclasse de Conta, podendo escolher a instância em tempo de execução através de métodos factory por exemplo.

Segue um link que pode ser útil: https://www.caelum.com.br/apostila-java-orientacao-objetos/heranca-reescrita-e-polimorfismo/

2 curtidas

Entendo, mas se eu tiver um método em algum lugar do código que receba como parâmetro uma “Conta c” (como é o exemplo do link que você me passou), tanto os objetos instanciados como ContaCorrente c1 = new ContaCorrente(); e os instanciados como Conta c1 = new ContaCorrente(); serão instanciados, não? Ao menos ao fazer o teste assim funcionou. Ainda estou meio confuso nesse parte. Em qual a utilidade prática de usar uma referência menos específica na hora de instanciar uma subclasse filha de uma classe abstrata…

Apenas complementando o que já foi muito bem dito pelo Bruno_Almeida, a diferença é exatamente em deixar as coisas mais genéricas possível. Veja, uma conta corrente é uma conta, assim como uma conta poupança tb. Toda conta consegue sacar, mas o processo de saque de uma CC pode ser diferente de uma CP. Vamos ao exemplo:

Pensemos num cenário onde um job agendado debita mensalmente uma taxa de manutenção de conta, mas esta taxa de manutenção varia conforme o tipo de conta do usuário, que pode ser uma conta comum ou uma conta vip. Se o usuário tiver uma conta comum, é descontado do saldo atual o valor de 10,00, mas se o cliente tiver uma conta VIP, é descontado a bagatela de 5,00 do saldo atual. Veja, toda conta deve ter um método de desconto da taxa de manutenção, mas a lógica do cálculo é diferente para cada uma delas. Façamos o seguinte pra iniciar: criar uma classe conta com o atributo saldo e um método abstrato aplicarTaxaManutencao().

public abstract class Conta {
    protected double saldo;

    public abstract void aplicarTaxaManutencao();

    public double getSaldo() {
        return saldo;
    }

    public void setSaldo(double saldo) {
        this.saldo = saldo;
    }
}

Agora criamos as classes especializadas:

public class ContaComum extends Conta {
    @Override
    public void aplicarTaxaManutencao() {
        this.saldo -= 10.0;
    }
}

public class ContaVip extends Conta {
    @Override
    public void aplicarTaxaManutencao() {
        this.saldo -= 5.0;
    }
}

Como disse lá em cima, a rotina de descontar a taxa de manutenção roda mensalmente em várias contas, ou seja, numa lista de contas. Imagina se vc tiver que verificar cada tipo de classe antes de aplicar a taxa de manutenção. Muita complexidade sem motivo, não acha? Quando vc estende uma classe pai ou implementa uma interface, a classe que o faz estabelece um relacionamento IS-A com a classe pai ou interface, e é exatamente por isso que é possível instanciar: Conta conta = new ContaComum(), pois ContaComum IS-A Conta. A aplicação prática do polimorfismo nesse nosso cenário entra em saber que a taxa de manutenção da conta tem que ser aplicada, independente do tipo de conta. Logo, posso receber uma lista de contas (Conta) e mandar aplicar a taxa, sem me preocupar com o tipo do objeto, pois é garantido que o método aplicarTaxaManutencao() foi sobrescrito em cada subclasse. Eu poderia fazer então:

List<Conta> contas = new ArrayList();
for(Conta conta : contas){
    conta.aplicarTaxaManutencao();
}

Concluindo, não há erro conceitual alguma na aplicação da forma como colocou! Abraço

1 curtida

Ah sim, consegui compreender.

Muito obrigado pela ajuda, amigos!