1. Sobrescrita de método
Na vida, é comum que o “descendente” se comporte de modo especial. Por exemplo, todos os animais sabem emitir sons, mas o gato — “miau”, o cachorro — “au au”, e o programador — “ih, bug de novo!”. Em programação, isso é implementado por meio da sobrescrita de método (override).
Sobrescrita de método é quando uma subclasse fornece sua própria implementação de um método que já foi declarado na classe-pai. Ou seja, “substitui” o comportamento padrão por algo mais específico.
Analogia. Se imaginarmos a classe-pai como uma receita assinada de borscht, então a sobrescrita é quando a avó adiciona o ingrediente secreto dela. Continua sendo borscht, mas o sabor de cada um é particular.
Para sobrescrever um método, é preciso declarar na classe filha um método com exatamente a mesma assinatura (nome, parâmetros, tipo de retorno) que o do pai.
Exemplo: animais e seus sons
class Animal {
void makeSound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
// Sobrescrevemos o método makeSound()
void makeSound() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
// Sobrescrevemos o método makeSound()
void makeSound() {
System.out.println("Meow!");
}
}
Agora, se você criar um objeto Dog e chamar makeSound(), você ouvirá "Woof!", e não "Some generic animal sound".
Demonstração em código
public class Main {
public static void main(String[] args) {
Animal generic = new Animal();
Dog dog = new Dog();
Cat cat = new Cat();
generic.makeSound(); // Some generic animal sound
dog.makeSound(); // Woof!
cat.makeSound(); // Meow!
}
}
Importante: se a subclasse não tiver um método com a mesma assinatura, será utilizado o método do pai.
2. Anotação @Override: para que serve e como usar
Em Java, é comum marcar métodos sobrescritos com a anotação @Override. Não é apenas um enfeite no código, mas uma ferramenta útil:
- O compilador verifica se você realmente está sobrescrevendo um método do pai. Se houver erro no nome, no tipo de parâmetro ou no tipo de retorno — o compilador emitirá um erro.
- Melhora a legibilidade do código. Outro programador vê imediatamente: “Ah, este método sobrescreve o do pai”.
Exemplo com @Override
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
Se você escrever acidentalmente void makeSond() (erro de digitação!) em um método anotado com @Override, o compilador reclamará: "Method does not override or implement a method from a supertype".
Padrões modernos. Usar @Override é uma boa prática e um padrão da indústria. Mesmo que o compilador não exija, sempre coloque essa anotação — isso facilita a vida para você e para os colegas.
3. Como funciona a chamada de um método sobrescrito
Quando você chama um método em um objeto da subclasse, será usada a implementação da subclasse, mesmo que a variável seja declarada como o tipo do pai.
Exemplo: polimorfismo em ação
Animal animal = new Dog();
animal.makeSound(); // "Woof!", e não "Some generic animal sound"
Aqui a variável é do tipo Animal, mas na verdade ela guarda um objeto Dog. O Java “entende” que deve chamar o método sobrescrito de Dog. Isso é polimorfismo (mais detalhes nas próximas aulas).
4. Restrições e regras de sobrescrita
Assinatura do método
- O nome, o tipo e a ordem dos parâmetros devem coincidir com os do método no pai.
- O tipo de retorno deve coincidir ou ser covariante (subtipo do tipo de retorno do pai). Por exemplo, se o pai retorna Animal e o filho retorna Dog, isso é permitido.
Modificadores de acesso
- Não é permitido tornar o acesso mais restrito do que no pai.
- Se o método do pai é public, então o sobrescrito também deve ser public.
- Se o método do pai é protected, o sobrescrito pode ser protected ou public.
Se você tentar fazer o contrário, o compilador dirá: "Cannot reduce the visibility of the inherited method".
Exceções
- O método sobrescrito não pode lançar uma nova checked exception que não esteja na declaração do pai.
- É permitido lançar menos exceções do que o pai, ou seus subtipos.
static, final, private
- Não é possível sobrescrever métodos declarados como static ou final, nem métodos privados (private).
- static — é ocultação (hiding), não sobrescrita.
- final — não pode ser sobrescrito de forma alguma; o Java protege tais métodos.
- private — não é visível no filho, não pode ser sobrescrito (só é possível declarar um novo método com o mesmo nome).
Construtores
Construtores não são herdados nem sobrescritos. Cada classe tem seus próprios construtores.
5. Evoluindo o aplicativo didático “Zoológico”
Hora de aplicar a teoria na prática! Vamos continuar evoluindo nosso aplicativo de “zoológico”.
Etapa 1. Classe base Animal
public class Animal {
public void makeSound() {
System.out.println("Some generic animal sound");
}
public void sleep() {
System.out.println("Zzz...");
}
}
Etapa 2. Subclasses Dog e Cat
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
// Método adicional apenas para Dog
public void fetch() {
System.out.println("Dog brings the stick!");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow!");
}
// Método adicional apenas para Cat
public void scratch() {
System.out.println("Cat scratches the sofa!");
}
}
Etapa 3. Usando a sobrescrita
public class ZooTest {
public static void main(String[] args) {
Animal generic = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
generic.makeSound(); // Some generic animal sound
dog.makeSound(); // Woof!
cat.makeSound(); // Meow!
// dog.fetch(); // Erro! A variável do tipo Animal não conhece fetch()
// cat.scratch(); // Análogo
// Mas se especificarmos o tipo explicitamente:
if (dog instanceof Dog) {
((Dog) dog).fetch(); // Dog brings the stick!
}
if (cat instanceof Cat) {
((Cat) cat).scratch(); // Cat scratches the sofa!
}
}
}
Comentário:
O método makeSound() funciona de forma polimórfica — é chamada a versão do método do tipo real do objeto. Já métodos específicos (fetch, scratch) ficam disponíveis apenas via casting explícito — isso é importante para entender como herança e sobrescrita funcionam.
6. Exemplo com tipo de retorno (covariância)
Às vezes queremos que o método sobrescrito retorne um tipo mais “estreito”. Por exemplo:
class Animal {
Animal getFriend() {
return new Animal();
}
}
class Dog extends Animal {
@Override
Dog getFriend() { // Tipo de retorno — Dog, subtipo de Animal
return new Dog();
}
}
Isso se chama covariância do tipo de retorno e é permitido no Java (desde o Java 5).
7. O que acontece se você não usar @Override?
Se você errar no nome do método ou nos parâmetros, o Java não reclamará se não houver a anotação @Override. Como resultado, você não sobrescreverá, mas criará um novo método, e o comportamento esperado não mudará.
Exemplo de erro
class Dog extends Animal {
// Erro de digitação: makeSoud em vez de makeSound
void makeSoud() {
System.out.println("Woof!");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.makeSound(); // Imprimirá "Some generic animal sound"
}
}
Se houvesse @Override, o compilador emitiria um erro: "Method does not override or implement a method from a supertype".
8. Erros típicos ao sobrescrever métodos
Erro nº 1: ausência da anotação @Override.
Sem ela é fácil errar no nome do método ou nos parâmetros. Como resultado, o método não será sobrescrito e o programa não se comportará como você esperava.
Erro nº 2: tentar restringir o modificador de acesso.
Se o método do pai é public e você escreve protected ou private — ocorrerá erro de compilação.
Erro nº 3: assinatura não correspondente.
Se os parâmetros diferirem ao menos no tipo — isso já não é sobrescrita, mas sobrecarga (overloading).
Erro nº 4: tentar sobrescrever um método final ou static.
O Java não permite: final protege contra sobrescrita, e métodos static não são sobrescritos (apenas ocultados).
Erro nº 5: mudar o tipo de retorno para um incompatível.
Só é permitido retornar um subtipo do tipo de retorno do pai (covariância), e não um tipo completamente diferente.
GO TO FULL VERSION