Herança é reutilização do tipo "é-um"
A herança permite que uma nova classe seja construída sobre uma já existente. A nova classe -a subclasse- recebe automaticamente os campos e métodos da superclasse e, em seguida, adiciona ou altera o que for necessário. Você recorre a ela quando um tipo é uma forma mais específica de outro: um Dog é um Animal, uma SavingsAccount é uma BankAccount.
Você a escreve com a palavra-chave extends. Tudo que for public ou protected na classe pai fica disponível na classe filha sem precisar reescrever.
Dog nunca declara name nem eat(), mas mesmo assim tem ambos. É exatamente esse o objetivo: o comportamento compartilhado fica em um só lugar.
super: alcançando a classe pai
O construtor de uma subclasse precisa garantir que a classe pai seja inicializada primeiro. Você faz isso com super(...), que chama o construtor da classe pai e precisa ser a primeira instrução do construtor da subclasse. Se você o omitir, o Java insere uma chamada silenciosa ao construtor sem argumentos da classe pai - e, se a classe pai não tiver esse construtor, o código não compila.
A saída mostra que o construtor da classe pai é executado antes do corpo da classe filha - a construção flui do topo da hierarquia para baixo.
Sobrescrevendo métodos
Uma subclasse pode substituir um método herdado redefinindo-o com a mesma assinatura. Isso é a sobrescrita, e você deve sempre marcá-la com @Override. A anotação não é obrigatória, mas faz o compilador verificar se você realmente corresponde a um método da classe pai - ela detecta erros de digitação como tostring() em vez de toString(), que de outra forma criariam silenciosamente um método totalmente novo.
Mesmo que o array seja do tipo Animal, cada elemento executa o seu próprio speak(). O Java escolhe o método com base no objeto real em tempo de execução, não no tipo declarado da variável - essa é a base do polimorfismo.
Chamando a versão da classe pai com super.method()
Sobrescrever não precisa descartar o trabalho da classe pai. Use super.method() para executar a versão herdada e, em seguida, ampliá-la:
Sem o super., chamar log dentro de TimestampLogger.log chamaria a si mesmo e entraria em recursão infinita. super. significa explicitamente "a versão da classe pai".
Campos herdados e acesso
Uma subclasse enxerga os membros public e protected da classe pai, mas não os private. Os campos private ainda existem no objeto - os próprios métodos da classe pai podem acessá-los - mas a subclasse não pode referenciá-los diretamente. Use protected quando quiser que as subclasses tenham acesso, mantendo ao mesmo tempo um membro escondido de código não relacionado.
class Base {
private int secret; // invisível para as subclasses
protected int shared; // visível para as subclasses
}
class Derived extends Base {
void demo() {
shared = 5; // OK
// secret = 5; // erro de compilação - private em Base
}
}
É também por isso que o construtor de uma subclasse muitas vezes precisa chamar super(...): é a única forma de inicializar o estado privado da classe pai.
Impedindo a herança com final
Às vezes uma classe não deve poder ser estendida de jeito nenhum - String é final exatamente por esse motivo. Marcar uma classe como final proíbe criar subclasses; marcar um método como final proíbe sobrescrevê-lo, embora ainda permita estender a classe.
final class Constants { } // não pode ter subclasses
class Config {
final void load() { } // as subclasses podem estender Config
// mas não podem sobrescrever load()
}
Recorra a final quando o comportamento de uma classe precisar ser garantido e imutável em todo o programa - é um sinal deliberado de "não estenda", não a opção padrão.
Uma armadilha comum: prefira composição quando não é "é-um"
A herança é tentadora porque reutiliza código, mas acopla fortemente a classe filha à classe pai. Se a relação não for um genuíno "é-um" - digamos, um Car que por acaso precisa de um Engine - não faça Car extends Engine. Um carro tem um motor, ele não é um motor. Modele isso com um campo (composição) em vez disso:
class Car {
private Engine engine = new Engine(); // Car TEM-UM Engine
void start() { engine.ignite(); }
}
Use herança somente quando a subclasse for realmente uma forma especializada da superclasse e você quiser herdar e substituir o comportamento dela.
Próximo: Interfaces
A herança com extends lhe dá uma única classe pai e uma implementação compartilhada. Mas uma classe só pode estender uma classe - então como dar uma capacidade comum a classes não relacionadas? É para isso que existem as interfaces: um contrato que muitas classes podem implementar, e o tema da próxima página.
Perguntas frequentes
O que é herança em Java?
A herança permite que uma classe (a subclasse) reutilize os campos e métodos de outra classe (a superclasse) usando a palavra-chave extends. A subclasse recebe automaticamente os membros públicos e protegidos da classe pai e pode adicionar novos ou substituir o comportamento herdado por meio da sobrescrita. Ela modela uma relação "é-um": um Dog é um Animal.
O que a palavra-chave super faz em Java?
super se refere à classe pai. super(...) dentro de um construtor chama o construtor da classe pai (e precisa ser a primeira instrução), enquanto super.method() chama a versão da classe pai de um método que você sobrescreveu. Isso permite que a subclasse se apoie na lógica da classe pai em vez de substituí-la por completo.
Qual é a diferença entre sobrescrita e sobrecarga em Java?
A sobrescrita redefine um método herdado em uma subclasse usando a mesma assinatura, mudando o comportamento - marque-a com @Override. A sobrecarga define vários métodos com o mesmo nome, mas com listas de parâmetros diferentes, na mesma classe. A sobrescrita tem a ver com herança e despacho em tempo de execução; a sobrecarga são apenas dois métodos que por acaso compartilham um nome.