Olá Alisson,
Vamos considerar o seguinte exemplo:
public abstract class Funcionario {
public abstract double getBonificacao();
public void mostraBonificacao() {
System.out.println(getBonificacao());
}
Nesse caso, se conseguíssemos instanciar a classe Funcionario, o que aconteceria se tentássemos invocar o método mostraBonificacao()?
Teríamos um problema porque o funcionario não tem o método getBonificacao() implementado! Por isso, quando temos pelo menos um método abstrato em nossas classes, temos que torná-la abstrata. Fazendo isso, agora não conseguimos mas instanciar a classe Funcionario e não precisamos nos preocupar com o problema citado acima.
Mas e se tivermos classes que herdam de Funcionario? Por exemplo, um Gerente:
public class Gerente extends Funcionario {
...
}
Como a classe Funcionario tem um método abstrato, o Gerente será forçado a implementá-lo:
public class Gerente extends Funcionario {
public double getBonificacao() {
return 500.0;
}
}
Agora, o que vai acontecer quando invocarmos o método mostraBonificacao() no Gerente?
Nesse caso não temos problema algum! O método mostraBonificacao() agora só pode ser invocado em classes que herdam de Funcionario (ele é abstrato). Todas as classes que herdam de Funcionario precisam implementar o getBonificacao(). Logo, quando invocarmos o mostraBonificacao(), o método getBonificacao() sempre vai existir!
A vantagem de fazer isso é que conseguimos deixar um comportamento padrão para todos funcionários na classe Funcionario (mostraBonificacao) e deixamos a parte que pode variar de acordo com o cargo (getBonificacao) para que seja implementada nas subclasses do Funcionario.
Dessa forma podemos implementar algoritmos bem complexos em uma classe base e permitir que partes desse algoritmo sejam customizadas nas classes filhas.
Espero ter ajudado!