W3docs

Herança em Java

Reutilize e estenda o comportamento de classes em Java com a palavra-chave extends e as regras de herança simples.

A herança permite que uma classe construa sobre outra em vez de começar do zero. A nova classe — a subclasse — herda todos os campos e métodos do pai, e acrescenta (ou substitui) apenas o que é diferente. É assim que Java expressa relacionamentos do tipo "é um": um Cat é um Animal, um AdminUser é um User.

A palavra-chave é extends. A mecânica é simples. A parte difícil — e o que este capítulo realmente aborda — é reconhecer quando a herança é a ferramenta certa e quando não é.

Um primeiro exemplo

public class Animal {
  String name;
  void breathe() { System.out.println(name + " breathes"); }
}

public class Cat extends Animal {
  void purr() { System.out.println(name + " purrs"); }
}

Cat declara apenas purr(). Ainda assim, ela possui name e breathe(), porque os herdou:

Cat c = new Cat();
c.name = "Mittens";
c.breathe();     // Mittens breathes — inherited from Animal
c.purr();        // Mittens purrs   — declared on Cat

Animal é a superclasse (ou pai), Cat é a subclasse (ou filha). Outras linguagens as chamam de classe base/derivada.

O que é herdado

Uma subclasse herda:

  • Todos os campos e métodos public e protected de cada ancestral.
  • Campos e métodos com visibilidade de pacote, se a subclasse estiver no mesmo pacote.
  • Todos os membros herdados mantêm seus modificadores originais.

Uma subclasse não herda:

  • Construtores. Eles não são membros no mesmo sentido — a subclasse precisa dos seus próprios.
  • Campos e métodos private. Eles existem no pai (o layout de memória de um Cat ainda inclui quaisquer campos private de Animal), mas a subclasse não pode vê-los pelo nome. A única forma de acessá-los é por meio de métodos acessores public/protected herdados.

Herança simples — apenas um pai

Toda classe estende exatamente uma outra classe. Não há herança múltipla para classes em Java:

public class Hybrid extends Animal, Vehicle { }    // ERROR — no multiple inheritance

Você pode implementar muitas interfaces (abordadas em interfaces) — isso oferece a flexibilidade de múltiplas fontes que a maioria das linguagens usa herança múltipla para obter, sem as complicações de ambiguidade.

Se você não escrever extends, a classe implicitamente estende Object:

public class Foo { }
// is equivalent to
public class Foo extends Object { }

É por isso que todo objeto Java possui toString(), equals(), hashCode() e getClass() — todos estão em Object. O capítulo sobre a classe Object os aborda em detalhes.

Classes que não podem ser estendidas

Uma classe marcada com final não pode ser um pai de forma alguma — tentar estendê-la é um erro de compilação:

public final class Money { }

public class Coupon extends Money { }   // ERROR — cannot inherit from final Money

Isso é intencional, não um descuido: muitos tipos fundamentais são final justamente para que nenhuma subclasse possa alterar seu comportamento. String, Integer e as outras classes wrapper são todas final, o que faz parte do que as torna seguras para compartilhar e armazenar em cache. Quando você quiser um tipo que não possa ser criado em subclasse — geralmente por razões de segurança ou imutabilidade — marque-o com final.

Construtores e super

Todo construtor de subclasse deve, como sua primeira ação, chamar um construtor do pai. Se você não escrever a chamada, Java insere super() automaticamente:

public class Animal {
  String name;
  public Animal(String name) { this.name = name; }
}

public class Cat extends Animal {
  public Cat(String name) {
    super(name);             // call Animal(String)
  }
}

Se Animal não tivesse um construtor sem argumentos e Cat não escrevesse super(...), o compilador reclamaria — não há Animal() para inserir implicitamente. O capítulo sobre a palavra-chave super cobre o encadeamento de construtores com super(...) e as chamadas super.method() em detalhes.

Sobrescrita

Uma subclasse pode substituir um método herdado declarando um com a mesma assinatura:

public class Animal {
  String speak() { return "(some noise)"; }
}

public class Cat extends Animal {
  @Override
  String speak() { return "meow"; }
}

Cat c = new Cat();
System.out.println(c.speak());   // meow

@Override é uma anotação que diz ao compilador "pretendo que este método sobrescreva um herdado — por favor, gere um erro se não for o caso." Sempre use-a. Ela detecta erros de digitação e incompatibilidades de assinatura que, de outra forma, criariam silenciosamente um novo método em vez de sobrescrever o antigo. O capítulo sobre sobrescrita de métodos cobre as regras completas.

Upcasting e polimorfismo

Uma instância de subclasse pode ser atribuída a uma variável do tipo pai:

Animal a = new Cat();    // upcast — implicit
a.speak();               // calls Cat's speak() — picked at runtime

Este é o fundamento do polimorfismo, o próximo capítulo. A variável a é do tipo Animal, mas o objeto real é um Cat, então a versão de Cat de speak é executada.

Quando a herança é a ferramenta errada

A herança é o mecanismo mais abusado na POO. Alguns sinais de alerta de que você deveria usar composição (um campo de outro tipo) em vez disso:

  • A subclasse não passa realmente em um teste "é um". Um Stack não é realmente um Vector — ainda assim, java.util.Stack estende Vector e é amplamente considerado um erro de design.
  • Os internos mutáveis do pai vazam para a subclasse. Mudanças na implementação do pai quebram a subclasse.
  • Você está herdando para reutilizar alguns métodos, não porque os tipos são genuinamente substituíveis.

A regra de ouro do Effective Java de Joshua Bloch: prefira composição à herança. Se B precisa do comportamento de A mas não é realmente um A, dê a B um campo privado do tipo A e redirecione o que for necessário.

// Inheritance — fragile
public class MyList extends ArrayList<String> { ... }

// Composition — robust
public class MyList {
  private final List<String> inner = new ArrayList<>();
  public void add(String s) { inner.add(s); }
}

A versão com composição é imune a surpresas quando ArrayList adiciona novos métodos ou muda como seus campos privados funcionam.

Herança e acesso

Membros herdados mantêm o modificador que tinham no pai. Uma subclasse não pode restringir a visibilidade de um método sobrescrito — tornar um método public em private na subclasse violaria o princípio de substituição de Liskov, e o compilador recusa:

public class A {
  public void hello() { }
}
public class B extends A {
  private void hello() { }    // ERROR — cannot reduce visibility
}

Você pode ampliar a visibilidade (sobrescrever um método protected como public), mas raramente deveria.

Um exemplo completo

java— editable, runs on the server

O que vem a seguir

super apareceu várias vezes aqui — no encadeamento de construtores e como forma de acessar um método pai sobrescrito. O próximo capítulo sobre a palavra-chave super percorre todas as situações em que ela aparece. Quando um pai deve definir o que seus filhos fazem, mas não como, você recorre a classes abstratas, que se baseiam diretamente nas regras de herança abordadas aqui.

Prática

Prática
Qual afirmação sobre herança em Java é verdadeira?
Qual afirmação sobre herança em Java é verdadeira?
Was this page helpful?