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:
// 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:
// 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:
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.
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.
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:
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:
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:
// 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:
// 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:
// 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:
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:
// 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:
// 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:
// 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
// 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
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
// 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
// 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!