JAVA Learning

Hub de Conteúdo

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:

java
// 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

java
// 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

java
// 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

java
// 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

java
// 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

java
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

java
// 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

java
// 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:

java
// 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! 🎓