Abstração
O Quarto Pilar da Programação Orientada a Objetos - Esconda complexidade, mostre o essencial
1. O que é Abstração?
Abstração é o processo de esconder detalhes complexos e mostrar apenas o essencial de um objeto. É como usar um carro sem saber como o motor funciona.
Em POO, abstraímos através de classes abstratas e interfaces, que definem "o quê fazer" sem especificar "como fazer".
A abstração permite criar code mais genérico, reutilizável e fácil de manter, focando no que realmente importa.
2. Conceitos Fundamentais
Contrato
Define quais métodos uma classe deve implementar, sem especificar como.
Implementação
As subclasses fornecem a implementação específica dos métodos abstratos.
Generalização
Extrair características comuns de múltiplas classes para uma classe abstrata.
Extensão
Criar classes especializadas que herdam da classe abstrata.
3. Classes Abstratas
Uma classe abstrata é um template que não pode ser instanciada. Serve como base para subclasses.
Características:
- Não pode ser instanciada diretamente
- Pode ter métodos abstratos (sem implementação)
- Pode ter métodos concretos (com implementação)
- Pode ter atributos
- Usada com palavra-chave abstract
Exemplo Básico:
// Classe Abstrata
public abstract class Funcionario {
protected String nome;
protected double salarioBase;
public Funcionario(String nome, double salarioBase) {
this.nome = nome;
this.salarioBase = salarioBase;
}
// Método abstrato - sem implementação
public abstract double calcularSalario();
// Método abstrato
public abstract void trabalhar();
// Método concreto - com implementação
public void exibir() {
System.out.println("Nome: " + nome);
System.out.println("Salário: R$ " +
String.format("%.2f", calcularSalario()));
}
// Método concreto
public void folga() {
System.out.println(nome + " está em folga!");
}
}
// ERRADO - Não pode instanciar
// Funcionario f = new Funcionario("João", 3000); // ERRO!
// Subclasse concreta
public class Gerente extends Funcionario {
private double bonus;
public Gerente(String nome, double salarioBase, double bonus) {
super(nome, salarioBase);
this.bonus = bonus;
}
@Override
public double calcularSalario() {
return salarioBase + bonus;
}
@Override
public void trabalhar() {
System.out.println(nome + " está gerenciando a equipe");
}
}
// Subclasse concreta
public class Desenvolvedor extends Funcionario {
private int linguagens;
public Desenvolvedor(String nome, double salarioBase, int linguagens) {
super(nome, salarioBase);
this.linguagens = linguagens;
}
@Override
public double calcularSalario() {
return salarioBase + (linguagens * 500); // Bônus por linguagem
}
@Override
public void trabalhar() {
System.out.println(nome + " está codificando");
}
}
// Uso correto
Funcionario gerente = new Gerente("Maria", 5000, 2000);
Funcionario dev = new Desenvolvedor("João", 4000, 5);
gerente.trabalhar(); // Maria está gerenciando a equipe
dev.trabalhar(); // João está codificando
gerente.exibir();
dev.exibir();
4. Métodos Abstratos
Métodos abstratos não têm corpo e devem ser implementados pelas subclasses.
Regras para Métodos Abstratos:
- Só podem existir em classes abstratas
- Terminam com ponto-e-vírgula (;)
- Subclasses devem implementá-los obrigatoriamente
- Podem ter modificadores de acesso (public, protected)
Exemplo Prático: Sistema de Pagamento
// Classe Abstrata que define o contrato
public abstract class FormaPagamento {
protected double valor;
protected String data;
public FormaPagamento(double valor) {
this.valor = valor;
this.data = java.time.LocalDate.now().toString();
}
// Método abstrato - Cada forma tem sua validação
public abstract boolean validar();
// Método abstrato - Cada forma processa diferente
public abstract void processar();
// Método abstrato - Cada forma tem seu recibo
public abstract String gerarRecibo();
// Método concreto - comum para todas
public void exibir() {
System.out.println("Valor: R$ " + String.format("%.2f", valor));
System.out.println("Data: " + data);
}
}
// Cartão de Crédito
public class CartaoCredito extends FormaPagamento {
private String numero;
private String bandeira;
public CartaoCredito(double valor, String numero, String bandeira) {
super(valor);
this.numero = numero;
this.bandeira = bandeira;
}
@Override
public boolean validar() {
return numero.length() == 16 && !bandeira.isEmpty();
}
@Override
public void processar() {
if (validar()) {
System.out.println("Processando " + bandeira + "...");
System.out.println("Cartão: **** **** **** " +
numero.substring(12));
System.out.println("Pagamento aprovado!");
} else {
System.out.println("Cartão inválido!");
}
}
@Override
public String gerarRecibo() {
return "Recibo - Cartão " + bandeira + " - R$ " +
String.format("%.2f", valor);
}
}
// PIX
public class Pix extends FormaPagamento {
private String chave;
public Pix(double valor, String chave) {
super(valor);
this.chave = chave;
}
@Override
public boolean validar() {
return chave != null && chave.length() > 0;
}
@Override
public void processar() {
if (validar()) {
System.out.println("Processando PIX para: " + chave);
System.out.println("Transferência instantânea!");
} else {
System.out.println("Chave PIX inválida!");
}
}
@Override
public String gerarRecibo() {
return "Recibo - PIX para " + chave + " - R$ " +
String.format("%.2f", valor);
}
}
// Boleto
public class Boleto extends FormaPagamento {
private String codigo;
public Boleto(double valor, String codigo) {
super(valor);
this.codigo = codigo;
}
@Override
public boolean validar() {
return codigo != null && codigo.length() == 47;
}
@Override
public void processar() {
if (validar()) {
System.out.println("Boleto gerado com sucesso!");
System.out.println("Código: " + codigo);
System.out.println("Vencimento: 3 dias úteis");
} else {
System.out.println("Código de boleto inválido!");
}
}
@Override
public String gerarRecibo() {
return "Recibo - Boleto " + codigo + " - R$ " +
String.format("%.2f", valor);
}
}
// Uso
FormaPagamento[] pagamentos = new FormaPagamento[3];
pagamentos[0] = new CartaoCredito(100, "1234567890123456", "Visa");
pagamentos[1] = new Pix(50, "email@example.com");
pagamentos[2] = new Boleto(150, "12345678901234567890123456789012345678901234");
for (FormaPagamento p : pagamentos) {
p.exibir();
p.processar();
System.out.println(p.gerarRecibo());
System.out.println();
}
5. Interfaces
Uma interface é um contrato ainda mais abstrato que uma classe abstrata. Define apenas o que deve ser feito, nunca o como.
Características:
- Todos os métodos são abstratos (implicitamente)
- Não pode ter estado mutável (apenas constantes)
- Uma classe pode implementar múltiplas interfaces
- Usada com palavra-chave interface e implements
Exemplo: Sistema de Eletrodomésticos
// Interface 1
public interface Ligavel {
void ligar();
void desligar();
boolean estaLigado();
}
// Interface 2
public interface AquecedorAr {
void aquecer();
void esfriar();
void setTemperatura(int temp);
}
// Interface 3
public interface Economico {
double calcularConsumo();
}
// Classe que implementa múltiplas interfaces
public class Microondas implements Ligavel, AquecedorAr, Economico {
private boolean ligado;
private int temperatura;
private int potencia;
@Override
public void ligar() {
ligado = true;
System.out.println("Microondas ligado!");
}
@Override
public void desligar() {
ligado = false;
System.out.println("Microondas desligado!");
}
@Override
public boolean estaLigado() {
return ligado;
}
@Override
public void aquecer() {
if (ligado) {
temperatura += 10;
System.out.println("Aquecendo para " + temperatura + "°C");
}
}
@Override
public void esfriar() {
if (ligado) {
temperatura -= 5;
System.out.println("Esfriando para " + temperatura + "°C");
}
}
@Override
public void setTemperatura(int temp) {
this.temperatura = temp;
System.out.println("Temperatura ajustada para " + temp + "°C");
}
@Override
public double calcularConsumo() {
return potencia * 1.5; // Consumo em kWh
}
}
// Outra classe implementando as mesmas interfaces
public class Geladeira implements Ligavel, AquecedorAr, Economico {
private boolean ligado;
private int temperatura;
@Override
public void ligar() {
ligado = true;
System.out.println("Geladeira ligada!");
}
@Override
public void desligar() {
ligado = false;
System.out.println("Geladeira desligada!");
}
@Override
public boolean estaLigado() {
return ligado;
}
@Override
public void aquecer() {
if (ligado && temperatura < -5) {
temperatura++;
System.out.println("Temperatura: " + temperatura + "°C");
}
}
@Override
public void esfriar() {
if (ligado) {
temperatura--;
System.out.println("Temperatura: " + temperatura + "°C");
}
}
@Override
public void setTemperatura(int temp) {
this.temperatura = temp;
System.out.println("Temperatura ajustada para " + temp + "°C");
}
@Override
public double calcularConsumo() {
return 150; // Consumo fixo
}
}
// Uso
Ligavel microondas = new Microondas();
Ligavel geladeira = new Geladeira();
// Usa interface Ligavel
microondas.ligar();
geladeira.ligar();
// Acessa métodos da interface AquecedorAr
AquecedorAr m = (AquecedorAr) microondas;
m.aquecer();
m.setTemperatura(80);
// Acessa métodos da interface Economico
Economico mic = (Economico) microondas;
System.out.println("Consumo: " + mic.calcularConsumo() + " kWh");
6. Classes Abstratas vs Interfaces
| Aspecto | Classe Abstrata | Interface |
|---|---|---|
| Instância | Não pode ser instanciada | Não pode ser instanciada |
| Herança | Uma classe abstrata só | Múltiplas interfaces |
| Métodos | Abstratos e concretos | Apenas abstratos |
| Atributos | Qualquer tipo | Apenas constantes (final) |
| Construtores | Pode ter | Não tem |
| Modificadores | private, protected, public | Apenas public |
| Uso | Relacionamento "É UM" | Comportamento compartilhado |
| Exemplo | Animal → Cachorro | Ligavel, Comivel |
Quando Usar?
Use Classe Abstrata quando:
- Há código compartilhado entre classes
- Há relacionamento "É UM" entre as classes
- Precisa de construtores ou métodos protegidos
- Precisa de estado mutável (atributos)
Use Interface quando:
- Quer definir um contrato de comportamento
- Classes não relacionadas precisam compartilhar métodos
- Precisa de múltipla herança de tipo
- Apenas define "o quê" fazer, não "como"
7. Exemplo Completo: Sistema de Banco
// Interface para operações básicas
public interface ContaBancaria {
void depositar(double valor);
void sacar(double valor);
double getSaldo();
}
// Interface para juros
public interface ComJuros {
void aplicarJuros();
double getTaxaJuros();
}
// Classe Abstrata - Base para todas as contas
public abstract class Conta implements ContaBancaria {
protected String numero;
protected String titular;
protected double saldo;
public Conta(String numero, String titular, double saldoInicial) {
this.numero = numero;
this.titular = titular;
this.saldo = saldoInicial;
}
// Métodos abstratos - cada conta tem suas regras
public abstract double calcularTarifa();
public abstract boolean podeUltrapassarLimite();
@Override
public void depositar(double valor) {
if (valor > 0) {
saldo += valor;
System.out.println("Depósito de R$ " +
String.format("%.2f", valor) + " realizado!");
}
}
@Override
public double getSaldo() {
return saldo - calcularTarifa();
}
public void exibir() {
System.out.println("Conta: " + numero);
System.out.println("Titular: " + titular);
System.out.println("Saldo: R$ " +
String.format("%.2f", getSaldo()));
}
}
// Conta Corrente
public class ContaCorrente extends Conta implements ComJuros {
private static final double TAXA_DIARIA = 10;
private static final double TAXA_JUROS = 0.5;
public ContaCorrente(String numero, String titular, double saldoInicial) {
super(numero, titular, saldoInicial);
}
@Override
public void sacar(double valor) {
if (valor <= saldo) {
saldo -= valor;
System.out.println("Saque de R$ " +
String.format("%.2f", valor) + " realizado!");
} else {
System.out.println("Saldo insuficiente!");
}
}
@Override
public double calcularTarifa() {
return TAXA_DIARIA;
}
@Override
public boolean podeUltrapassarLimite() {
return true;
}
@Override
public void aplicarJuros() {
if (saldo > 0) {
double juros = saldo * (TAXA_JUROS / 100);
saldo += juros;
System.out.println("Juros aplicados: R$ " +
String.format("%.2f", juros));
}
}
@Override
public double getTaxaJuros() {
return TAXA_JUROS;
}
}
// Conta Poupança
public class ContaPoupanca extends Conta implements ComJuros {
private static final double TAXA_JUROS = 0.8;
private static final double TAXA_DIARIA = 0;
public ContaPoupanca(String numero, String titular, double saldoInicial) {
super(numero, titular, saldoInicial);
}
@Override
public void sacar(double valor) {
if (valor <= saldo && saldo - valor >= 0) {
saldo -= valor;
System.out.println("Saque de R$ " +
String.format("%.2f", valor) + " realizado!");
} else {
System.out.println("Saque não permitido!");
}
}
@Override
public double calcularTarifa() {
return TAXA_DIARIA;
}
@Override
public boolean podeUltrapassarLimite() {
return false;
}
@Override
public void aplicarJuros() {
double juros = saldo * (TAXA_JUROS / 100);
saldo += juros;
System.out.println("Juros aplicados: R$ " +
String.format("%.2f", juros));
}
@Override
public double getTaxaJuros() {
return TAXA_JUROS;
}
}
// Uso
ContaBancaria conta1 = new ContaCorrente("1234", "João", 1000);
ContaBancaria conta2 = new ContaPoupanca("5678", "Maria", 500);
conta1.depositar(500);
conta1.sacar(200);
conta2.depositar(300);
conta2.sacar(100);
// Aplicar juros
ComJuros cc = (ComJuros) conta1;
cc.aplicarJuros();
ComJuros cp = (ComJuros) conta2;
cp.aplicarJuros();
// Exibir
((Conta) conta1).exibir();
System.out.println();
((Conta) conta2).exibir();
8. Boas Práticas com Abstração
✓ FAÇA:
- Use abstração para esconder complexidade
- Defina contratos claros com classes abstratas/interfaces
- Coloque comportamento comum em classes abstratas
- Use interfaces para definir comportamentos desejáveis
- Documente bem o que cada método abstrato deve fazer
- Use nomes significativos para abstrações
- Implemente todos os métodos abstratos obrigatoriamente
✗ EVITE:
- Criar classes abstratas sem métodos abstratos
- Abstrair quando não há repetição de padrões
- Interfaces muito grandes com muitos métodos
- Abstrair detalhes de implementação sem benefício
- Ignorar a implementação obrigatória de métodos abstratos
- Deixar métodos abstratos sem documentação
9. Erros Comuns com Abstração
❌ Erro 1: Instanciar Classe Abstrata
// ERRADO - Não pode instanciar
public abstract class Animal { }
Animal animal = new Animal(); // ERRO!
// CORRETO - Instancia a subclasse concreta
Animal animal = new Cachorro();
❌ Erro 2: Não Implementar Método Abstrato
public abstract class Funcionario {
public abstract double calcularSalario();
}
// ERRADO - Não implementou método abstrato
public class Gerente extends Funcionario {
// Falta implementar calcularSalario()
}
// CORRETO - Implementa o método
public class Gerente extends Funcionario {
@Override
public double calcularSalario() {
return 5000;
}
}
❌ Erro 3: Criar Classe Abstrata Desnecessária
// DESNECESSÁRIO - Apenas uma subclasse
public abstract class Pessoa { }
public class Cliente extends Pessoa { }
// MELHOR - Apenas classe concreta
public class Pessoa { }
public class Cliente extends Pessoa { }
❌ Erro 4: Confundir Herança com Abstração
// ERRADO - Confunde conceitos
public class Veiculo { } // Classe concreta
public class Carro extends Veiculo { }
// CORRETO - Usa abstração
public abstract class Veiculo {
public abstract void acelerar();
}
public class Carro extends Veiculo {
@Override
public void acelerar() { }
}
10. Quando Usar Abstração
✓ Quando Usar Classe Abstrata:
- Vários objetos compartilham características
- Há hierarquia clara de classes
- Quer impor um contrato com implementação padrão
- Exemplo: Animal → Cachorro, Gato, Passaro
✓ Quando Usar Interface:
- Quer definir um comportamento que qualquer classe pode ter
- Classes sem relacionamento implementam mesma interface
- Quer máxima flexibilidade
- Exemplo: Ligavel (Microondas, TV, Geladeira)
Exemplo Prático:
// Classe Abstrata - Hierarquia
public abstract class Veiculo {
protected String marca;
public abstract void mover();
}
// Interface - Comportamento
public interface Comeco {
void ligar();
void desligar();
}
// Implementação
public class Carro extends Veiculo implements Comeco {
@Override
public void mover() { }
@Override
public void ligar() { }
@Override
public void desligar() { }
}
// Barco pode implementar a mesma interface
public class Barco implements Comeco {
@Override
public void ligar() { }
@Override
public void desligar() { }
}
11. Exercícios Práticos
Exercício 1: Sistema de Animais
Crie classe abstrata Animal com métodos abstratos fazer som() e mover(). Crie subclasses Cachorro, Passaro e Peixe que implementem.
Exercício 2: Formas Geométricas
Crie interface Forma com métodos calcularArea() e calcularPerimetro(). Implemente em Circulo, Quadrado e Triangulo.
Exercício 3: Sistema de Relatórios
Crie classe abstrata Relatorio com métodos abstratos gerar() e exportar(). Crie subclasses RelatorioPDF, RelatorioExcel e RelatorioHTML.
Exercício 4: Múltiplas Interfaces
Crie interfaces Persistivel (salvar/carregar) e Validavel (validar). Crie classe Usuario que implemente ambas as interfaces.
12. Resumo - Os 4 Pilares da POO
1. Encapsulamento
Protege dados com private, controla acesso com getters/setters.
2. Herança
Reutiliza código criando relacionamentos hierárquicos entre classes.
3. Polimorfismo
Um mesmo método com múltiplos comportamentos (sobrecarga e sobrescrita).
4. Abstração
Esconde complexidade, mostra apenas o essencial (classes abstratas e interfaces).
Conclusão
Abstração completa o trio de pilares da POO, permitindo criar código genérico, flexível e fácil de manter.
Os 4 Pilares trabalham juntos:
- Encapsulamento protege dados
- Herança reutiliza código
- Polimorfismo oferece flexibilidade
- Abstração simplifica complexidade
Dominar esses 4 pilares torna você um programador Java profissional! 🎓