W3docs

Sobrescrita de Métodos em Java

Sobrescreva métodos da classe pai em subclasses Java com a anotação @Override e despacho dinâmico.

Sobrescrita ocorre quando uma subclasse declara um método com a mesma assinatura de um herdado, substituindo a versão do pai. Combinada com polimorfismo, é o mecanismo que permite chamar um método em uma referência do tipo pai e ter a implementação correta da subclasse sendo executada.

Este capítulo define as regras: o que conta como sobrescrita, o que não conta, o que o compilador permite alterar na versão da subclasse e o que @Override realmente oferece.

Como é uma sobrescrita

Um método sobrescreve um herdado quando todos estes requisitos são satisfeitos:

  • Mesmo nome.
  • Mesma lista de parâmetros (tipos e ordem, após o apagamento de genéricos).
  • Tipo de retorno igual ao do pai ou um subtipo dele (retorno covariante).
  • Visibilidade igual à do pai ou mais ampla.
  • Não declarado como final, static ou private no pai.
class Animal {
  String speak() { return "(noise)"; }
}
class Cat extends Animal {
  @Override
  String speak() { return "meow"; }
}

Esta é uma sobrescrita válida. Chamar speak() em um Cat retorna "meow"; chamá-lo por meio de uma referência Animal que aponta para um Cat também retorna "meow" — o despacho é decidido pela classe do objeto real.

@Override — use sempre

@Override é uma anotação que diz ao compilador "pretendo que isto sobrescreva um método herdado." Se não sobrescrever de fato — nome errado, parâmetros errados, tipo de retorno errado — o compilador emite um erro:

class Cat extends Animal {
  @Override
  String Speak() { return "meow"; }    // ERROR — no Speak() in Animal
}

Sem a anotação, isso seria compilado silenciosamente como um método novo chamado Speak, e ((Animal)cat).speak() continuaria retornando o "(noise)" do pai. A anotação não tem custo e detecta toda uma família de bugs silenciosos. Sempre escreva-a nas sobrescritas.

Nota

As sobrescritas que você mais escreverá não são das suas próprias classes — são de métodos herdados de Object, a raiz de toda classe. Sobrescrever toString(), equals(Object) e hashCode() é como seus objetos se imprimem de forma legível, comparam por valor e se comportam corretamente como chaves em um HashMap ou HashSet. @Override nesses métodos detecta o bug clássico de escrever equals(Cat other) (uma sobrecarga) em vez de equals(Object other) (a sobrescrita real).

Sobrescrita vs sobrecarga

AspectoSobrescritaSobrecarga
OndeEntre um par subclasse / superclasseNa mesma classe
AssinaturaDeve corresponder à do paiDeve diferir das demais
DespachoEm tempo de execução, baseado no objeto realEm tempo de compilação, baseado nos tipos dos argumentos
Anotação@Overridenenhuma

Esses conceitos são frequentemente confundidos. Sobrescrita é um método, vários tipos de objeto. Sobrecarga é vários métodos, um tipo, escolhido pelo que você passa.

Tipos de retorno covariantes

A subclasse pode declarar um tipo de retorno mais restrito que o pai:

class Animal {
  Animal makeBaby() { return new Animal(); }
}
class Cat extends Animal {
  @Override
  Cat makeBaby() { return new Cat(); }      // returns Cat, not Animal — fine
}

Isso é covariância — o tipo de retorno da sobrescrita é um subtipo do pai. É seguro porque qualquer chamador que espera um Animal de makeBaby() aceita tranquilamente um Cat. O benefício é que chamadores que sabem que têm um Cat recebem um Cat de volta sem conversão de tipo:

Cat kitten = new Cat().makeBaby();    // no cast needed

Fazer o contrário (uma subclasse ampliando o tipo de retorno) não é permitido — isso quebraria os chamadores que esperavam o tipo mais restrito.

A visibilidade só pode ser ampliada

A sobrescrita deve ter ao menos a mesma visibilidade do método do pai:

class A { public  void hi() { } }
class B extends A { protected void hi() { } }   // ERROR — reduces visibility
class C extends A { public    void hi() { } }   // ok

Restringir a visibilidade significaria que um chamador com uma referência A poderia chamar hi(), mas, após a atribuição A a = new B(), não poderia mais — o que quebraria a substitutibilidade.

As exceções só podem diminuir

Se o método pai não declara exceções verificadas, a sobrescrita também não pode declarar nenhuma. Se o pai lança uma exceção verificada, a sobrescrita pode lançar a mesma ou uma subclasse mais específica dela — mas não uma mais ampla:

class A {
  void run() throws IOException { ... }
}
class B extends A {
  @Override void run() throws FileNotFoundException { ... }    // ok — FileNotFoundException extends IOException
}
class C extends A {
  @Override void run() throws Exception { ... }                // ERROR — too broad
}

Exceções não verificadas (subclasses de RuntimeException) são irrestritas — você sempre pode lançá-las a partir de uma sobrescrita.

O que não pode ser sobrescrito

  • Métodos private. Eles não são visíveis para a subclasse, então um método com o mesmo nome na subclasse é simplesmente um novo método.
  • Métodos final. Marcados especificamente para impedir sobrescritas.
  • Métodos static. Uma subclasse pode declarar um método estático com o mesmo nome e assinatura, mas isso é chamado de ocultamento de método, não sobrescrita. Não há polimorfismo — a JVM resolve a chamada com base no tipo em tempo de compilação da referência:
class A { static String klass() { return "A"; } }
class B extends A { static String klass() { return "B"; } }

A a = new B();
System.out.println(a.klass());   // "A" — static, not polymorphic

Este é um dos poucos lugares onde a regra "o despacho de método escolhe a versão do objeto real" não se aplica.

Chamando o pai a partir da sobrescrita

Um padrão comum é estender, em vez de substituir, o comportamento do pai com super.method(...):

class Logger {
  void log(String s) { System.out.println(s); }
}
class TimestampedLogger extends Logger {
  @Override
  void log(String s) {
    super.log("[" + System.currentTimeMillis() + "] " + s);
  }
}

A sobrescrita decora o comportamento do pai em vez de duplicá-lo. Abordado no capítulo sobre a palavra-chave super.

Um exemplo completo

java— editable, runs on the server

O que vem a seguir

Você já viu como subclasses substituem métodos do pai. A próxima ideia — abstração — é o lado inverso: declarar um método que não tem nenhum corpo padrão e forçar cada subclasse concreta a fornecer um. Continue em abstração em Java.

Prática

Prática
Em uma subclasse, por que vale a pena escrever @Override em toda sobrescrita mesmo que o código compile sem ela?
Em uma subclasse, por que vale a pena escrever @Override em toda sobrescrita mesmo que o código compile sem ela?
Was this page helpful?