JAVA Learning

Hub de Conteúdo

Herança

O Segundo Pilar da Programação Orientada a Objetos - Reutilize código, organize hierarquias

1. O que é Herança?

Herança é o mecanismo que permite que uma classe herde características e comportamentos de outra classe. A classe que é herdada é chamada Classe Pai (Superclasse), e a classe que herda é chamada Classe Filha (Subclasse).

A herança promove reutilização de código e permite criar hierarquias de classes que representam relacionamentos do mundo real.

Exemplo: Uma classe Veiculo pode ser a Superclasse, e Carro, Moto e Caminhao podem ser Subclasses que herdam dela.

2. Sintaxe Básica de Herança

Em Java, usamos a palavra-chave extends para criar uma classe filha:

Sintaxe:
java
// Classe Pai (Superclasse)
public class Veiculo {
    protected String marca;
    protected String modelo;
    protected int ano;
    
    public Veiculo(String marca, String modelo, int ano) {
        this.marca = marca;
        this.modelo = modelo;
        this.ano = ano;
    }
    
    public void exibir() {
        System.out.println("Marca: " + marca);
        System.out.println("Modelo: " + modelo);
        System.out.println("Ano: " + ano);
    }
}

// Classe Filha (Subclasse)
public class Carro extends Veiculo {
    private int numPortas;
    
    public Carro(String marca, String modelo, int ano, int numPortas) {
        super(marca, modelo, ano); // Chama o construtor da classe pai
        this.numPortas = numPortas;
    }
    
    public void exibir() {
        super.exibir(); // Chama o método da classe pai
        System.out.println("Número de Portas: " + numPortas);
    }
}

Conceitos Importantes:

  • extends: Palavra-chave que indica herança
  • super: Referencia a classe pai (construtor, métodos)
  • protected: Modificador que permite acesso na classe e subclasses

3. Herança Simples - Exemplo Prático

Vamos criar um sistema de Animais com herança simples:

java
// Classe Pai - Animal
public class Animal {
    protected String nome;
    protected int idade;
    protected String cor;
    
    public Animal(String nome, int idade, String cor) {
        this.nome = nome;
        this.idade = idade;
        this.cor = cor;
    }
    
    public void fazer som() {
        System.out.println("Som genérico de animal");
    }
    
    public void dormir() {
        System.out.println(nome + " está dormindo...");
    }
    
    public void comer() {
        System.out.println(nome + " está comendo...");
    }
    
    public void info() {
        System.out.println("Nome: " + nome);
        System.out.println("Idade: " + idade);
        System.out.println("Cor: " + cor);
    }
}

// Classe Filha - Cachorro
public class Cachorro extends Animal {
    private String raca;
    
    public Cachorro(String nome, int idade, String cor, String raca) {
        super(nome, idade, cor); // Chama construtor da classe pai
        this.raca = raca;
    }
    
    @Override // Anotação que indica sobrescrita de método
    public void fazer som() {
        System.out.println(nome + " faz: Au au au!");
    }
    
    public void trazer() {
        System.out.println(nome + " está trazendo a bola!");
    }
    
    @Override
    public void info() {
        super.info();
        System.out.println("Raça: " + raca);
    }
}

// Classe Filha - Gato
public class Gato extends Animal {
    private boolean pelos_longos;
    
    public Gato(String nome, int idade, String cor, boolean pelos_longos) {
        super(nome, idade, cor);
        this.pelos_longos = pelos_longos;
    }
    
    @Override
    public void fazer som() {
        System.out.println(nome + " faz: Miau miau!");
    }
    
    public void arranhar() {
        System.out.println(nome + " está arranhando!");
    }
    
    @Override
    public void info() {
        super.info();
        System.out.println("Pelos Longos: " + (pelos_longos ? "Sim" : "Não"));
    }
}

// Classe Filha - Passaro
public class Passaro extends Animal {
    private double altitude;
    
    public Passaro(String nome, int idade, String cor) {
        super(nome, idade, cor);
        this.altitude = 0;
    }
    
    @Override
    public void fazer som() {
        System.out.println(nome + " faz: Piu piu!");
    }
    
    public void voar(double alt) {
        this.altitude = alt;
        System.out.println(nome + " está voando a " + alt + " metros!");
    }
    
    @Override
    public void info() {
        super.info();
        System.out.println("Altitude Atual: " + altitude + "m");
    }
}

Usando as Classes:

java
Cachorro dog = new Cachorro("Rex", 5, "Marrom", "Labrador");
Gato gato = new Gato("Mimi", 3, "Branco", true);
Passaro passaro = new Passaro("Tweety", 2, "Amarelo");

// Métodos herdados da classe pai
dog.dormir();      // Rex está dormindo...
gato.comer();      // Mimi está comendo...

// Métodos sobrescritos (overridden)
dog.fazer som();   // Rex faz: Au au au!
gato.fazer som();  // Mimi faz: Miau miau!
passaro.fazer som(); // Tweety faz: Piu piu!

// Métodos específicos da subclasse
dog.trazer();      // Rex está trazendo a bola!
gato.arranhar();   // Mimi está arranhando!
passaro.voar(100); // Tweety está voando a 100 metros!

// Informações completas
dog.info();
gato.info();
passaro.info();

4. Super e This - Referências Importantes

Palavra-chave THIS

Referencia o objeto atual da classe. É usado para diferenciar atributos da classe de parâmetros.

java
public class Pessoa {
    private String nome;
    
    public Pessoa(String nome) {
        this.nome = nome; // this.nome = atributo da classe
    }
    
    public void setNome(String nome) {
        this.nome = nome; // this diferencia do parâmetro
    }
    
    public void exibir() {
        System.out.println("Meu nome é: " + this.nome);
    }
}

Palavra-chave SUPER

Referencia a classe pai. É usado para chamar construtores e métodos da classe pai.

java
public class Funcionario extends Pessoa {
    private double salario;
    
    public Funcionario(String nome, double salario) {
        super(nome); // Chama construtor da classe pai
        this.salario = salario;
    }
    
    @Override
    public void exibir() {
        super.exibir(); // Chama método da classe pai
        System.out.println("Salário: R$ " + salario);
    }
    
    public void aumentarSalario(double percentual) {
        this.salario = this.salario + (this.salario * percentual / 100);
    }
}

5. @Override - Sobrescrita de Métodos

A anotação @Override indica que você está sobrescrevendo um método da classe pai. Não é obrigatória, mas é uma boa prática.

Exemplo de Sobrescrita:

java
public class Veiculo {
    protected String marca;
    
    public void acelerar() {
        System.out.println("Veículo acelerando...");
    }
}

public class Carro extends Veiculo {
    @Override
    public void acelerar() {
        System.out.println("Carro acelerando: Vruuuuuum!");
    }
}

public class Bicicleta extends Veiculo {
    @Override
    public void acelerar() {
        System.out.println("Bicicleta acelerando: Pedalando mais rápido!");
    }
}

// Uso
Veiculo carro = new Carro();
carro.acelerar(); // Carro acelerando: Vruuuuuum!

Veiculo bicicleta = new Bicicleta();
bicicleta.acelerar(); // Bicicleta acelerando: Pedalando mais rápido!

Regras para Sobrescrita:

  • Assinatura igual: Nome e parâmetros devem ser iguais
  • Tipo de retorno: Deve ser o mesmo ou um subtipo (Covariant)
  • Acessibilidade: Não pode ser mais restritiva
  • Exceções: Pode lançar menos exceções, não mais

6. Modificador protected em Herança

O modificador protected permite que membros sejam acessados dentro da classe e por subclasses, mesmo em pacotes diferentes:

java
public class Pessoa {
    private String cpf;           // Não é acessível em subclasses
    protected String nome;         // Acessível em subclasses
    public String email;           // Acessível em qualquer lugar
    
    public void exibir() {
        System.out.println(cpf);   // OK - mesma classe
        System.out.println(nome);  // OK - mesma classe
        System.out.println(email); // OK - mesma classe
    }
}

public class Funcionario extends Pessoa {
    public void info() {
        // System.out.println(cpf);   // ERRO - private não herda
        System.out.println(nome);     // OK - protected herda
        System.out.println(email);    // OK - public herda
    }
}

// Em outro pacote
Pessoa p = new Pessoa();
p.email = "test@mail.com"; // OK - public
// p.nome = "João";        // ERRO - protected (não em subclasse)
// p.cpf = "123456789";    // ERRO - private

7. Criando Hierarquias de Classes

Vamos criar uma hierarquia completa de Funcionários em uma empresa:

java
// Classe Base
public class Funcionario {
    protected String matricula;
    protected String nome;
    protected double salarioBase;
    
    public Funcionario(String matricula, String nome, double salarioBase) {
        this.matricula = matricula;
        this.nome = nome;
        this.salarioBase = salarioBase;
    }
    
    public double calcularSalario() {
        return salarioBase;
    }
    
    public void exibir() {
        System.out.println("Matrícula: " + matricula);
        System.out.println("Nome: " + nome);
        System.out.println("Salário: R$ " + String.format("%.2f", calcularSalario()));
    }
}

// Funcionário Horista
public class Horista extends Funcionario {
    private double valorHora;
    private int horasTrabalhadas;
    
    public Horista(String matricula, String nome, double valorHora) {
        super(matricula, nome, 0);
        this.valorHora = valorHora;
        this.horasTrabalhadas = 0;
    }
    
    public void registrarHoras(int horas) {
        this.horasTrabalhadas += horas;
    }
    
    @Override
    public double calcularSalario() {
        return valorHora * horasTrabalhadas;
    }
}

// Funcionário Mensalista
public class Mensalista extends Funcionario {
    private int faltas;
    
    public Mensalista(String matricula, String nome, double salarioBase) {
        super(matricula, nome, salarioBase);
        this.faltas = 0;
    }
    
    public void registrarFalta() {
        this.faltas++;
    }
    
    @Override
    public double calcularSalario() {
        double desconto = (salarioBase / 30) * faltas;
        return salarioBase - desconto;
    }
}

// Gerente (estende Mensalista)
public class Gerente extends Mensalista {
    private double gratificacao;
    
    public Gerente(String matricula, String nome, double salarioBase, double gratificacao) {
        super(matricula, nome, salarioBase);
        this.gratificacao = gratificacao;
    }
    
    @Override
    public double calcularSalario() {
        return super.calcularSalario() + gratificacao;
    }
    
    @Override
    public void exibir() {
        super.exibir();
        System.out.println("Cargo: Gerente");
    }
}

// Diretor (estende Gerente)
public class Diretor extends Gerente {
    private double bonus;
    
    public Diretor(String matricula, String nome, double salarioBase, double gratificacao, double bonus) {
        super(matricula, nome, salarioBase, gratificacao);
        this.bonus = bonus;
    }
    
    @Override
    public double calcularSalario() {
        return super.calcularSalario() + bonus;
    }
    
    @Override
    public void exibir() {
        super.exibir();
        System.out.println("Cargo: Diretor");
    }
}

Usando a Hierarquia:

java
// Criando funcionários
Horista horista = new Horista("001", "João", 50);
horista.registrarHoras(160); // 160 horas no mês

Mensalista mensalista = new Mensalista("002", "Maria", 3000);
mensalista.registrarFalta();
mensalista.registrarFalta();

Gerente gerente = new Gerente("003", "Carlos", 5000, 1000);

Diretor diretor = new Diretor("004", "Ana", 8000, 2000, 5000);

// Exibindo informações
horista.exibir();
mensalista.exibir();
gerente.exibir();
diretor.exibir();

8. Tipo de Dados e Polimorfismo

Um objeto pode ser referenciado como tipo de qualquer classe pai na hierarquia:

java
// Polymorphism - Um único tipo para múltiplos objetos
Funcionario[] funcionarios = new Funcionario[4];

funcionarios[0] = new Horista("001", "João", 50);
funcionarios[1] = new Mensalista("002", "Maria", 3000);
funcionarios[2] = new Gerente("003", "Carlos", 5000, 1000);
funcionarios[3] = new Diretor("004", "Ana", 8000, 2000, 5000);

// Processando todos uniformemente
for (Funcionario f : funcionarios) {
    f.exibir();
    System.out.println();
}

// Cada um calcula seu salário corretamente (polimorfismo)
double folha = 0;
for (Funcionario f : funcionarios) {
    folha += f.calcularSalario();
}
System.out.println("Total da Folha: R$ " + String.format("%.2f", folha));

Verificar Tipo com instanceof:

java
for (Funcionario f : funcionarios) {
    if (f instanceof Diretor) {
        Diretor d = (Diretor) f; // Cast explícito
        System.out.println(d.nome + " é Diretor!");
    } else if (f instanceof Gerente) {
        System.out.println(f.nome + " é Gerente!");
    } else if (f instanceof Mensalista) {
        System.out.println(f.nome + " é Mensalista!");
    } else if (f instanceof Horista) {
        System.out.println(f.nome + " é Horista!");
    }
}

9. Restrições e Limitações da Herança

❌ Herança Múltipla NÃO é permitida em Java:

java
// ERRO - Herança Múltipla não é permitida!
// public class Pato extends Ave, Nadador { }

// SOLUÇÃO - Use Interfaces (veremos depois)
public interface Ave {
    void voar();
}

public interface Nadador {
    void nadar();
}

public class Pato implements Ave, Nadador {
    @Override
    public void voar() {
        System.out.println("Pato voando!");
    }
    
    @Override
    public void nadar() {
        System.out.println("Pato nadando!");
    }
}

❌ Herança em Cadeia Muito Profunda é ruim:

Evite hierarquias muito profundas (mais de 3-4 níveis). Fica difícil de entender e manter:

java
// Ruim - Muito profundo
public class A {}
public class B extends A {}
public class C extends B {}
public class D extends C {}
public class E extends D {}
public class F extends E {}

// Melhor - Hierarquia mais rasa
public class Animal {}
public class Mamifero extends Animal {}
public class Cachorro extends Mamifero {}

⚠️ Herança vs Composição:

Nem sempre herança é a melhor escolha. Às vezes, usar composição (ter um objeto dentro de outro) é melhor:

java
// Com Herança (problemático)
public class Veiculo {}
public class Carro extends Veiculo {} // Carro É UM Veículo
public class Garagem extends Veiculo {} // ERRADO! Garagem NÃO É UM Veículo

// Com Composição (correto)
public class Garagem {
    private List veiculos; // Garagem TEM veículos
}

// Dica: Use "É UM" para herança, "TEM UM" para composição

10. Boas Práticas com Herança

✓ FAÇA:

  • Use herança apenas quando há relacionamento "É UM" real
  • Mantenha hierarquias simples e rasas
  • Use @Override ao sobrescrever métodos
  • Use protected para membros que subclasses precisam acessar
  • Chame super() no construtor da subclasse
  • Considere usar composição como alternativa
  • Documente comportamentos esperados em métodos

✗ EVITE:

  • Criar hierarquias profundas (mais de 4 níveis)
  • Herança apenas para reutilizar código
  • Deixar atributos importantes como public
  • Quebrar o contrato da classe pai em subclasses
  • Usar herança quando composição seria melhor
  • Sobrescrever métodos sem necessidade

11. Erros Comuns em Herança

❌ Erro 1: Não chamar super() no construtor

java
// Ruim
public class Funcionario extends Pessoa {
    public Funcionario(String nome) {
        this.nome = nome; // ERRO - nome não foi inicializado na classe pai!
    }
}

// Correto
public class Funcionario extends Pessoa {
    public Funcionario(String nome) {
        super(nome); // Chama construtor da classe pai
    }
}

❌ Erro 2: Mudar assinatura de método

java
public class Animal {
    public void fazer som(String tipo) {
        System.out.println("Som: " + tipo);
    }
}

// Errado - Mudou a assinatura!
public class Cachorro extends Animal {
    public void fazer som() { // ERRO - sem parâmetro!
        System.out.println("Au au!");
    }
}

// Correto - Mantém a assinatura
public class Cachorro extends Animal {
    @Override
    public void fazer som(String tipo) {
        System.out.println("Cachorro faz: Au au!");
    }
}

❌ Erro 3: Deixar atributos como public

java
// Ruim
public class Pessoa {
    public String nome; // Public - fácil de mudar indevidamente
}

public class Funcionario extends Pessoa {
    public void info() {
        nome = null; // Alguém pode fazer isso!
    }
}

// Correto
public class Pessoa {
    protected String nome; // Protected - controlado
}

public class Funcionario extends Pessoa {
    public void setNome(String nome) {
        if (nome != null && !nome.isEmpty()) {
            this.nome = nome; // Com validação
        }
    }
}

❌ Erro 4: Herança quando deveria ser composição

java
// Errado - Garagem não É UM Carro
public class Carro {
    protected String marca;
}

public class Garagem extends Carro { // ERRADO!
    // Garagem não faz sentido como Carro
}

// Correto - Composição
public class Garagem {
    private List carros;
    
    public void adicionarCarro(Carro carro) {
        carros.add(carro);
    }
}

12. Exercícios Práticos

Exercício 1: Hierarquia de Formas Geométricas

Crie uma classe Forma com método calcularArea(). Crie subclasses Retangulo, Circulo e Triangulo. Cada uma deve sobrescrever o método.

Exercício 2: Sistema de Contas Bancárias

Crie classe Conta e subclasses ContaCorrente, ContaPoupanca e ContaSalario. Cada uma tem regras diferentes para saques e taxas.

Exercício 3: Hierarquia de Eletrônicos

Crie classe Eletronico e subclasses como Computador, Smartphone, Tablet. Implemente métodos para ligar, desligar e especificações.

Exercício 4: Herança com Validação

Crie classe Pessoa e subclasses Aluno e Professor. Use encapsulamento + herança. Cada um tem validações específicas.

13. Resumo - Conceitos Principais

O que é Herança?

Mecanismo que permite reutilizar código criando relacionamentos hierárquicos entre classes.

Classe Pai (Superclasse)

Classe que fornece atributos e métodos para subclasses.

Classe Filha (Subclasse)

Classe que herda de outra. Pode adicionar novos atributos/métodos ou sobrescrever.

Palavra-chave extends

Usada para indicar herança: public class Filha extends Pai {}

Super e This

super referencia a classe pai, this referencia o objeto atual.

@Override

Anotação que indica sobrescrita de método da classe pai (boa prática).

Protected

Modificador que permite acesso em subclasses, mesmo em pacotes diferentes.

Conclusão

Herança é fundamental para criar código reutilizável e bem organizado. Use para criar relacionamentos naturais entre classes, mantendo hierarquias simples.

Lembre-se: "É UM" = Herança | "TEM UM" = Composição!

Próximo passo: Estude Polimorfismo - o terceiro pilar que trabalha junto com herança!