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