Cara, isso é MUITO útil…em projeto orientado a objetos, herança/polimorfismo faz uma parte importantíssima…quando você precisa, por exemplo, representar um comportamento genérico que tem várias implementações diferentes, fica fácil colocar uma superclasse com a “interface” (os métodos) genérica e colocar cada implementação específica como uma subclasse. Dessa forma, a parte do programa que vai utilizar esse comportamento não precisa saber qual implementação está sendo usada, apenas como usá-la (o que fica definido na superclasse). Outra vantagem legal que surge disso é a manutenção…alterar, adicionar ou retirar comportamentos específicos diz respeito somente às subclasses que os implementam, e quem usa esse comportamento não precisará ser alterado.
Um exemplo legal de trabalho que eu tive que fazer pra faculdade pra entender a necessidade disso foi implementar um interpretador para uma linguagem que o professor criou: nós tínhamos uma lista de Comando, e essa classe tinha um método abstrato executa. Existem vários comandos na linguagem, mas todos eles são executados da mesma maneira:
Vector comandos = new Vector(); // lista de comandos
/* A lista de comandos é preenchida através da leitura e decodificação
do programa
...
*/
for (int i=0 ; i<comandos.size() ; i++) {
((Comando)comandos.elementAt(i)).executa();
}
Como eu tinha dito antes, cada comando tem sua implementação específica, como ComandoIf, ComandoFor, ComandoWriteln, etc…mas todos eles são executados da mesma forma, e o interpretador, ao executá-los, não precisa nem saber qual comando é.