Java Herança

Um cachorro e um gato são diferentes: um late, o outro mia. Mas os dois têm nome, idade e comem. Se você escrever uma classe Cachorro e uma Gato, vai repetir essa parte comum nas duas. A herança resolve isso: você escreve o que é comum uma vez, numa classe Animal, e diz que Cachorro e Gato são tipos de Animal. Eles recebem tudo de graça e só acrescentam o que têm de especial.

Você vai aprender
  • O que é herança e como escrevê-la com extends.
  • O teste do "é um", para saber quando usar herança (e quando não usar).
  • O que a subclasse herda e o que ela não enxerga (private).
  • Por que o Java só deixa herdar de uma classe, e o erro real de quem tenta duas.
  • A cadeia de herança, que sempre termina na classe Object.

Esta é a primeira aula do grupo "Herança e Polimorfismo". Ela supõe que você já sabe criar classes e objetos, com atributos e métodos.


A ideia: herdar características

Na biologia, um filho herda características dos pais. Em Java, uma classe pode herdar de outra: ela recebe automaticamente os atributos e métodos da outra, e pode acrescentar os seus.

NomeTambém chamada deNo exemplo
Superclasseclasse mãe, classe base, classe paiAnimal: o que todo animal tem
Subclasseclasse filha, classe derivadaCachorro, Gato: o específico de cada um

"Super" vem do latim, "acima": a superclasse fica acima na hierarquia. "Sub" é "abaixo". Uma superclasse pode ter muitas subclasses (Animal tem Cachorro, Gato, Passaro…), como uma mãe pode ter muitos filhos.

O problema: código repetido

Sem herança, as duas classes da Ana ficariam assim:

class Cachorro {
    String nome;
    int idade;

    void comer() {
        System.out.println(nome + " está comendo.");
    }

    void latir() {
        System.out.println(nome + ": Au au!");
    }
}

class Gato {
    String nome;                 // repetido
    int idade;                   // repetido

    void comer() {               // repetido
        System.out.println(nome + " está comendo.");
    }

    void miar() {
        System.out.println(nome + ": Miau!");
    }
}

Metade do código é cópia. E cópia dá trabalho: se a Ana quiser mudar a mensagem de comer(), precisa lembrar de mudar nos dois lugares (e, mais tarde, em Passaro, Peixe, Coelho…). Esquecer um é questão de tempo.


extends: herdando de Animal

Com herança, a parte comum vai para Animal, e cada subclasse diz extends Animal:

public class Main {
    public static void main(String[] args) {
        Cachorro rex = new Cachorro();
        rex.nome = "Rex";
        rex.idade = 3;
        rex.comer();
        rex.latir();

        Gato mimi = new Gato();
        mimi.nome = "Mimi";
        mimi.idade = 5;
        mimi.comer();
        mimi.miar();
    }
}

class Animal {
    String nome;
    int idade;

    void comer() {
        System.out.println(nome + " está comendo.");
    }
}

class Cachorro extends Animal {
    void latir() {
        System.out.println(nome + ": Au au!");
    }
}

class Gato extends Animal {
    void miar() {
        System.out.println(nome + ": Miau!");
    }
}
class Animal
A superclasse, uma classe comum. Nada nela diz que alguém vai herdar dela.
class Cachorro
A subclasse.
extends
"Estende", "amplia": Cachorro é uma versão ampliada de Animal. Ele recebe nome, idade e comer() sem escrever nada.
Animal
De quem herdar. Só pode haver uma classe aqui.
void latir()
O que só o cachorro tem. Repare que ele usa nome, que foi herdado.
$ java Main.java
Rex está comendo.          « comer() veio de Animal
Rex: Au au!                « latir() é do Cachorro
Mimi está comendo.         « o mesmo comer(), herdado pelo Gato
Mimi: Miau!

A classe Cachorro tem três linhas de código, mas um objeto Cachorro tem dois atributos e dois métodos. Agora, mudar a mensagem de comer() é mudar um lugar, e todos os animais mudam juntos.

A herança é uma via de mão única. O Cachorro sabe tudo o que o Animal sabe, mas o Animal não sabe latir:

$ javac Main.java
Main.java:5: error: cannot find symbol
        bicho.latir();
             ^
  symbol:   method latir()
  location: variable bicho of type Animal
1 error

Aqui, bicho era um new Animal(). O erro diz: "não achei o método latir() na variável bicho, do tipo Animal". Nem todo animal late.

Missão: crie a classe Passaro, que herda de Animal e tem um método voar() que mostra NOME está voando!. No main, crie um pássaro chamado Piu e chame comer() e voar(). A saída deve ter Piu está comendo. e Piu está voando!.

public class Main {
    public static void main(String[] args) {
        Cachorro rex = new Cachorro();
        rex.nome = "Rex";
        rex.comer();
        rex.latir();
        // crie aqui o pássaro Piu
    }
}

class Animal {
    String nome;
    int idade;

    void comer() {
        System.out.println(nome + " está comendo.");
    }
}

class Cachorro extends Animal {
    void latir() {
        System.out.println(nome + ": Au au!");
    }
}

// crie aqui a classe Passaro
public class Main {
    public static void main(String[] args) {
        Cachorro rex = new Cachorro();
        rex.nome = "Rex";
        rex.comer();
        rex.latir();
        Passaro piu = new Passaro();
        piu.nome = "Piu";
        piu.comer();
        piu.voar();
    }
}

class Animal {
    String nome;
    int idade;

    void comer() {
        System.out.println(nome + " está comendo.");
    }
}

class Cachorro extends Animal {
    void latir() {
        System.out.println(nome + ": Au au!");
    }
}

class Passaro extends Animal {
    void voar() {
        System.out.println(nome + " está voando!");
    }
}

O teste do "é um"

Herança é poderosa, e por isso é fácil exagerar. A pergunta que decide é: "um X é um Y?". Se a frase faz sentido sempre, a herança cabe.

FraseFaz sentido?Então…
Um Cachorro é um AnimalSimCachorro extends Animal
Uma ContaPoupanca é uma ContaSimContaPoupanca extends Conta
Um Gerente é um FuncionarioSimGerente extends Funcionario
Um Carro é um MotorNão: um carro tem um motorAtributo: Motor motor; dentro de Carro
Uma Turma é um AlunoNão: uma turma tem alunosAtributo: List<Aluno> alunos;

Quando a relação é "tem um", o certo é um atributo; isso se chama composição. Herdar de Motor só para "aproveitar" os métodos dele faria um Carro ter acelerarPistao() como se fosse dele, e qualquer lugar que espera um motor aceitaria um carro inteiro. Quando estiver em dúvida, prefira composição: é a recomendação clássica dos livros de orientação a objetos.


O que é herdado (e o que não é)

A subclasse herda os atributos e métodos da superclasse. Mas "herdar" e "enxergar" são coisas diferentes. Os membros private existem dentro do objeto, só que a subclasse não pode tocá-los. Veja: o Animal tem um número de chip privado.

Este código tem um erro de propósito
public class Main {
    public static void main(String[] args) {
        Cachorro rex = new Cachorro();
        rex.nome = "Rex";
        rex.mostrarSegredo();
    }
}

class Animal {
    String nome;
    private String chip = "BR-0042";

    String getChip() {
        return chip;
    }
}

class Cachorro extends Animal {
    void mostrarSegredo() {
        System.out.println(nome + " tem o chip " + chip);
    }
}
$ javac Main.java
Main.java:20: error: chip has private access in Animal
        System.out.println(nome + " tem o chip " + chip);
                                                   ^
1 error

Chip has private access in Animal = "chip tem acesso privado em Animal". O private quer dizer "só a própria classe": nem as filhas entram (veja Modificadores de Acesso). O chip está lá, dentro de todo cachorro; o caminho para chegar até ele é o método público que o Animal oferece:

public class Main {
    public static void main(String[] args) {
        Cachorro rex = new Cachorro();
        rex.nome = "Rex";
        System.out.println(rex.nome + " tem o chip " + rex.getChip());
    }
}

class Animal {
    String nome;
    private String chip = "BR-0042";

    String getChip() {
        return chip;
    }
}

class Cachorro extends Animal {
}
$ java Main.java
Rex tem o chip BR-0042        « a classe Cachorro está vazia e mesmo assim tem getChip()

Esse é o encapsulamento funcionando junto com a herança: a superclasse protege os dados dela até das subclasses e decide o que oferecer.

Membro da superclasseA subclasse herda?A subclasse pode usar direto?
publicSimSim
protectedSimSim (mesmo em outro pacote)
Sem modificador (de pacote)SimSó se estiver no mesmo pacote
privateO dado existe no objetoNão: só pelos métodos públicos/protegidos
ConstrutoresNãoSó chamando com super(…) (veja super)
Membros staticAcessíveis pelo nome da subclasseSim, mas continuam sendo da superclasse
protected: "da família"

protected ("protegido") fica entre public e private: a classe, as subclasses (em qualquer pacote) e as classes do mesmo pacote enxergam. É útil para atributos que as filhas precisam mexer, mas o resto do programa não. Na dúvida, comece com private + métodos, como no chip.

Os construtores merecem destaque: cada classe tem os seus. Como o Animal aqui não declara nenhum, tudo funciona sozinho. Quando a superclasse tem um construtor com parâmetros, a subclasse precisa chamá-lo com super(…): esse é o assunto da próxima aula, super.


Herança em vários níveis

Uma subclasse pode ser superclasse de outra. A Ana organiza: todo Mamifero é um Animal, e todo Cachorro é um Mamifero.

public class Main {
    public static void main(String[] args) {
        Cachorro rex = new Cachorro();
        rex.nome = "Rex";
        rex.respirar();
        rex.mamar();
        rex.latir();
    }
}

class Animal {
    String nome;

    void respirar() {
        System.out.println(nome + " respira (Animal)");
    }
}

class Mamifero extends Animal {
    void mamar() {
        System.out.println(nome + " mama (Mamifero)");
    }
}

class Cachorro extends Mamifero {
    void latir() {
        System.out.println(nome + " late (Cachorro)");
    }
}
$ java Main.java
Rex respira (Animal)        « herdado do "avô"
Rex mama (Mamifero)         « herdado da mãe
Rex late (Cachorro)         « dele mesmo

O Cachorro herda do Mamifero, que herda do Animal; então o Cachorro tem tudo dos dois. Na prática, cadeias com mais de três ou quatro níveis ficam difíceis de entender: a cada método, você precisa subir a escada para descobrir de onde ele veio.


Herança simples: só uma superclasse

Um morcego é um mamífero e voa como uma ave. A Ana tentou herdar das duas:

Este código tem um erro de propósito
public class Main {
    public static void main(String[] args) {
    }
}

class Mamifero {
}

class Ave {
}

class Morcego extends Mamifero, Ave {
}
$ javac Main.java
Main.java:12: error: '{' expected
class Morcego extends Mamifero, Ave {
                              ^
1 error

A mensagem parece estranha: '{' expected = "esperava {". O compilador leu extends Mamifero e esperava a chave que abre a classe; a vírgula não faz parte da gramática do extends. Ou seja: em Java, uma classe tem exatamente uma superclasse. Isso se chama herança simples.

Por que essa limitação? Linguagens com herança múltipla (como C++) têm o "problema do diamante": se Mamifero e Ave tiverem, cada um, um método mover() diferente, qual o Morcego herda? Os criadores do Java preferiram evitar a confusão. Para "ser várias coisas ao mesmo tempo", o Java tem as interfaces: uma classe pode implementar quantas quiser (class Morcego extends Mamifero implements Voador).


No topo de tudo: Object

E o Animal, que não tem extends, herda de quem? Toda classe sem extends herda, automaticamente, de java.lang.Object. É como se o compilador escrevesse class Animal extends Object por você. Dá para ver a escada inteira pedindo a cada classe a sua superclasse:

public class Main {
    public static void main(String[] args) {
        Cachorro rex = new Cachorro();
        System.out.println(rex.getClass());
        System.out.println(rex.getClass().getSuperclass());
        System.out.println(rex.getClass().getSuperclass().getSuperclass());
        System.out.println(rex.getClass().getSuperclass().getSuperclass().getSuperclass());
        System.out.println(rex.toString());
    }
}

class Animal {
}

class Cachorro extends Animal {
}
rex.getClass()
Devolve a classe do objeto: Cachorro.
.getSuperclass()
Devolve a superclasse de uma classe. Encadeado, sobe um degrau de cada vez.
rex.toString()
Um método que nem Animal nem Cachorro escreveram: veio de Object.
$ java Main.java
class Cachorro
class Animal
class java.lang.Object       « o topo da escada
null                        « Object não tem superclasse
Cachorro@6a396c1e           « toString() herdado de Object

Por isso todo objeto Java, de qualquer classe, já nasce com toString(), equals(), hashCode() e getClass(). O Cachorro@6a396c1e é o toString() padrão: nome da classe, @ e um número em hexadecimal (que muda de uma execução para outra). Esses métodos têm uma aula só para eles: A Classe Object.

A família completa do exemplo da aula fica assim:

Object
 └── Animal
      ├── Cachorro
      ├── Gato
      └── Passaro

Quando dá errado

"… has private access in …"
$ javac Main.java
Main.java:20: error: chip has private access in Animal
        System.out.println(nome + " tem o chip " + chip);
                                                   ^
1 error

O que aconteceu: a subclasse tentou usar um atributo (ou método) private da superclasse.

Como resolver: use um método público da superclasse (getChip()). Se você é o dono da superclasse e as filhas realmente precisam do atributo, mude para protected.

"'{' expected" numa linha com extends A, B

O que aconteceu: tentativa de herdar de duas classes. Java só permite uma.

Como resolver: escolha a superclasse que responde ao "é um" mais forte e transforme o outro papel numa interface (implements), ou num atributo (composição).

"cannot find symbol … method latir() … of type Animal"
$ javac Main.java
Main.java:5: error: cannot find symbol
        bicho.latir();
             ^
  symbol:   method latir()
  location: variable bicho of type Animal
1 error

O que aconteceu: o método existe na subclasse, mas a variável é do tipo da superclasse. A herança desce (filha recebe da mãe), nunca sobe.

Como resolver: use uma variável do tipo Cachorro. Variáveis do tipo da superclasse guardando objetos da subclasse são o assunto de Polimorfismo.

"cannot inherit from final …"

O que aconteceu: a superclasse é final, como String e Integer.

Como resolver: use composição: guarde um objeto dessa classe como atributo. Veja final.


Referência

Escrita / termoSignificado
class B extends AB herda de A: B é a subclasse, A a superclasse
"é um"Teste para usar herança: um B é um A?
"tem um"Use composição (atributo), não herança
private na superclasseExiste no objeto, mas a subclasse não acessa direto
protectedVisível para subclasses e para o mesmo pacote
extends A, BNão existe: herança simples (erro '{' expected)
Sem extendsHerda de java.lang.Object
obj.getClass().getSuperclass()A superclasse da classe do objeto

Exercícios

1. A palavra da herança

Complete para Moto herdar de Veiculo:

class Moto { }

2. O topo da escada

Qual é a superclasse de uma classe que não tem extends? (nome completo)

3. "Da família"

Complete com o modificador que deixa as subclasses acessarem o atributo, mas não o resto do programa (de outros pacotes):

double saldo;

4. Qual destas relações deve ser feita com herança?

5. Animal tem private int idade. O que acontece se um método de Cachorro (que estende Animal) usar idade direto?

6. Quantas superclasses diretas uma classe Java pode ter?

Missão final: a loja da Ana tem funcionários e gerentes. A classe Gerente foi escrita copiando tudo de Funcionario. Refaça: Gerente deve herdar de Funcionario (extends) e manter só o que é dele (o atributo bonus e o método ganhoTotal()). Apague o que ficou repetido. A saída deve continuar com Caio ganha 3500.0 e Bia ganha 9000.0.

public class Main {
    public static void main(String[] args) {
        Funcionario caio = new Funcionario();
        caio.nome = "Caio";
        caio.salario = 3500;
        caio.mostrar();

        Gerente bia = new Gerente();
        bia.nome = "Bia";
        bia.salario = 7000;
        bia.bonus = 2000;
        System.out.println(bia.nome + " ganha " + bia.ganhoTotal());
    }
}

class Funcionario {
    String nome;
    double salario;

    void mostrar() {
        System.out.println(nome + " ganha " + salario);
    }
}

class Gerente {
    String nome;
    double salario;
    double bonus;

    void mostrar() {
        System.out.println(nome + " ganha " + salario);
    }

    double ganhoTotal() {
        return salario + bonus;
    }
}
public class Main {
    public static void main(String[] args) {
        Funcionario caio = new Funcionario();
        caio.nome = "Caio";
        caio.salario = 3500;
        caio.mostrar();

        Gerente bia = new Gerente();
        bia.nome = "Bia";
        bia.salario = 7000;
        bia.bonus = 2000;
        System.out.println(bia.nome + " ganha " + bia.ganhoTotal());
    }
}

class Funcionario {
    String nome;
    double salario;

    void mostrar() {
        System.out.println(nome + " ganha " + salario);
    }
}

class Gerente extends Funcionario {
    double bonus;

    double ganhoTotal() {
        return salario + bonus;
    }
}

Resumo

  • Herança: class B extends A faz B receber os atributos e métodos de A. Escreva o comum uma vez, na superclasse.
  • Use herança quando "um B é um A". Quando "B tem um A", use um atributo (composição).
  • A subclasse acrescenta o que é dela; a superclasse não enxerga o que as filhas acrescentaram.
  • Membros private da superclasse existem no objeto, mas a subclasse só os alcança por métodos. Construtores não são herdados.
  • Java tem herança simples: uma superclasse por classe. Para mais papéis, interfaces.
  • Toda classe termina em java.lang.Object, de onde vêm toString(), equals() e companhia.