JAVA Learning

Hub de Conteúdo

Encapsulamento

O Primeiro Pilar da Programação Orientada a Objetos - Proteja seus dados

1. O que é Encapsulamento?

Encapsulamento é o conceito de esconder os detalhes internos de um objeto e controlar o acesso aos seus dados através de métodos públicos. É como uma cápsula que protege dados privados, permitindo que apenas o necessário seja acessado de fora.

Imagine uma cápsula de remédio: você não precisa saber exactamente como o medicamento está dentro, você só precisa engolir a cápsula. Da mesma forma, em POO, os usuários da classe não precisam saber como os dados estão organizados internamente.

O encapsulamento oferece segurança, controle e flexibilidade no design de suas classes.

2. Modificadores de Acesso

Em Java, existem quatro níveis de visibilidade para membros de uma classe:

public

Acessível de qualquer lugar do código

Usar para: métodos que você quer que outros usem

private

Acessível apenas dentro da própria classe

Usar para: dados sensíveis e lógica interna

protected

Acessível dentro do pacote e por subclasses

Usar para: dados que subclasses precisam acessar

default (sem modificador)

Acessível apenas dentro do mesmo pacote

Usar para: membros do pacote

Tabela de Visibilidade

Modificador Mesma Classe Mesmo Pacote Subclasse (outro pacote) Outro Pacote
public
protected
default
private

3. Sem Encapsulamento (❌ Errado)

Vamos ver um exemplo de código SEM encapsulamento e seus problemas:

java
public class ContaBancaria {
    // Dados públicos - PÉSSIMA PRÁTICA!
    public String titular;
    public double saldo;
    public String numero;
    
    public ContaBancaria(String titular, double saldo, String numero) {
        this.titular = titular;
        this.saldo = saldo;
        this.numero = numero;
    }
}

Problemas desta Abordagem:

  • Sem Proteção: Qualquer um pode modificar o saldo diretamente
  • Sem Validação: Não há verificação se os valores são válidos
  • Sem Controle: Impossível rastrear mudanças nos dados
  • Sem Flexibilidade: Se precisar mudar a implementação, quebra todo código dependente
java
// Código danoso possível SEM encapsulamento
ContaBancaria conta = new ContaBancaria("João", 1000, "12345");

// Alguém pode fazer isto:
conta.saldo = -9999; // ERRO! Saldo negativo absurdo
conta.titular = null; // ERRO! Sem titular
conta.saldo = Double.POSITIVE_INFINITY; // ERRO! Valor infinito

4. Com Encapsulamento (✓ Correto)

Agora vamos ver o mesmo código COM encapsulamento:

java
public class ContaBancaria {
    // Dados PRIVADOS - Protegidos!
    private String titular;
    private double saldo;
    private String numero;
    
    // Construtor
    public ContaBancaria(String titular, double saldo, String numero) {
        setTitular(titular);       // Usa setter para validar
        setSaldo(saldo);           // Usa setter para validar
        this.numero = numero;
    }
    
    // GETTERS - Apenas para LEITURA
    public String getTitular() {
        return this.titular;
    }
    
    public double getSaldo() {
        return this.saldo;
    }
    
    public String getNumero() {
        return this.numero;
    }
    
    // SETTERS - Com VALIDAÇÃO
    public void setTitular(String titular) {
        if (titular != null && !titular.isEmpty()) {
            this.titular = titular;
        } else {
            System.out.println("Erro: Titular inválido!");
        }
    }
    
    public void setSaldo(double saldo) {
        if (saldo >= 0) {
            this.saldo = saldo;
        } else {
            System.out.println("Erro: Saldo não pode ser negativo!");
        }
    }
    
    // Métodos específicos para operações seguras
    public void depositar(double valor) {
        if (valor > 0) {
            this.saldo += valor;
            System.out.println("Depósito de R$ " + valor + " realizado com sucesso!");
        } else {
            System.out.println("Erro: Valor deve ser positivo!");
        }
    }
    
    public boolean sacar(double valor) {
        if (valor > 0 && valor <= this.saldo) {
            this.saldo -= valor;
            System.out.println("Saque de R$ " + valor + " realizado com sucesso!");
            return true;
        } else {
            System.out.println("Erro: Saque inválido!");
            return false;
        }
    }
}

Benefícios do Encapsulamento:

  • ✓ Proteção: Dados privados não podem ser acessados diretamente
  • ✓ Validação: Setters garantem que dados são válidos
  • ✓ Controle: Podemos rastrear e controlar todas as mudanças
  • ✓ Flexibilidade: Mudanças internas não afetam código externo
  • ✓ Segurança: Lógica de negócio protegida dentro da classe
java
// Usando a classe COM encapsulamento
ContaBancaria conta = new ContaBancaria("Maria", 1000, "54321");

// Operações seguras
conta.depositar(500);        // Funciona: 500 > 0
conta.sacar(200);            // Funciona: 200 <= 1500

// Tentativas perigosas são bloqueadas
conta.sacar(5000);           // Bloqueado: não há saldo
conta.depositar(-100);       // Bloqueado: valor negativo
conta.setTitular("");        // Bloqueado: vazio

// Acesso seguro aos dados
System.out.println(conta.getSaldo());     // Apenas LEITURA

5. Getters e Setters - Em Profundidade

Getters e Setters são métodos especiais que permitem acessar e modificar atributos privados de forma controlada:

O que é um Getter?

Um getter é um método que retorna o valor de um atributo privado. É usado apenas para LEITURA.

java
private String nome;
private int idade;

// Getters - Retornam valores (apenas leitura)
public String getNome() {
    return this.nome;
}

public int getIdade() {
    return this.idade;
}

// Uso:
Pessoa pessoa = new Pessoa("João", 30);
String nome = pessoa.getNome();  // Lê o valor
int idade = pessoa.getIdade();   // Lê o valor

O que é um Setter?

Um setter é um método que modifica o valor de um atributo privado. Geralmente inclui validação.

java
// Setters - Modificam valores com validação
public void setNome(String nome) {
    if (nome != null && !nome.isEmpty()) {
        this.nome = nome;
    } else {
        System.out.println("Erro: Nome inválido!");
    }
}

public void setIdade(int idade) {
    if (idade > 0 && idade < 150) {
        this.idade = idade;
    } else {
        System.out.println("Erro: Idade inválida!");
    }
}

// Uso:
Pessoa pessoa = new Pessoa("João", 30);
pessoa.setNome("Carlos");    // Modifica se válido
pessoa.setIdade(35);         // Modifica se válido
pessoa.setIdade(-5);         // REJEITADO: Idade negativa!

Padrão de Nomenclatura

  • Getter: get + Nome do Atributo (ex: getSaldo())
  • Setter: set + Nome do Atributo (ex: setSaldo())
  • Boolean: is + Nome (ex: isAtivo())

6. Exemplo Prático Completo: Classe Aluno

Vamos criar uma classe completa e bem encapsulada:

java
public class Aluno {
    private String matricula;
    private String nome;
    private double[] notas;
    private double media;
    private boolean aprovado;
    
    public Aluno(String matricula, String nome) {
        this.matricula = matricula;
        setNome(nome);
        this.notas = new double[4];
        this.media = 0;
        this.aprovado = false;
    }
    
    // ========== GETTERS ==========
    public String getMatricula() {
        return this.matricula;
    }
    
    public String getNome() {
        return this.nome;
    }
    
    public double getMedia() {
        return this.media;
    }
    
    public boolean isAprovado() {
        return this.aprovado;
    }
    
    // ========== SETTERS ==========
    public void setNome(String nome) {
        if (nome != null && nome.trim().length() >= 3) {
            this.nome = nome;
        } else {
            throw new IllegalArgumentException("Nome deve ter pelo menos 3 caracteres!");
        }
    }
    
    // ========== MÉTODOS ESPECÍFICOS ==========
    public void adicionarNota(int posicao, double nota) {
        if (posicao >= 0 && posicao < 4 && nota >= 0 && nota <= 10) {
            this.notas[posicao] = nota;
            calcularMedia();
        } else {
            System.out.println("Erro: Posição ou nota inválida!");
        }
    }
    
    private void calcularMedia() {
        double soma = 0;
        for (double nota : this.notas) {
            soma += nota;
        }
        this.media = soma / 4;
        this.aprovado = this.media >= 6.0;
    }
    
    public void exibirBoletim() {
        System.out.println("========== BOLETIM DO ALUNO ==========");
        System.out.println("Matrícula: " + this.matricula);
        System.out.println("Nome: " + this.nome);
        System.out.println("Média: " + String.format("%.2f", this.media));
        System.out.println("Status: " + (this.aprovado ? "APROVADO" : "REPROVADO"));
        System.out.println("====================================");
    }
}

Usando a Classe Aluno:

java
Aluno aluno = new Aluno("2024001", "Carlos Silva");

aluno.adicionarNota(0, 8.5);
aluno.adicionarNota(1, 7.0);
aluno.adicionarNota(2, 9.0);
aluno.adicionarNota(3, 6.5);

System.out.println("Média: " + aluno.getMedia());
System.out.println("Aprovado: " + aluno.isAprovado());

aluno.exibirBoletim();

7. Boas Práticas de Encapsulamento

✓ FAÇA:

  • Use private por padrão para atributos
  • Crie getters/setters apenas quando necessário
  • Valide dados nos setters
  • Use nomes descritivos e padronizados
  • Forneça métodos públicos úteis e seguros
  • Documente o comportamento esperado

✗ EVITE:

  • Deixar atributos públicos
  • Criar getter/setter para tudo
  • Getter que retorna array mutável
  • Setter sem validação
  • Expor lógica interna complexa
  • Nomes muito genéricos como "dados"

Cuidado com Arrays e Objetos Mutáveis

Retornar um array ou objeto diretamente permite que outros o modifiquem. Use cópia:

java
public class Turma {
    private Aluno[] alunos = new Aluno[30];
    
    // ❌ ERRADO - Retorna referência ao array original
    public Aluno[] getAlunos() {
        return this.alunos;  // Alguém pode modificar!
    }
    
    // ✓ CORRETO - Retorna cópia do array
    public Aluno[] getAlunos() {
        return this.alunos.clone();
    }
    
    // ✓ MELHOR - Retorna unmodifiable list
    public List getAlunosList() {
        return Collections.unmodifiableList(Arrays.asList(this.alunos));
    }
}

8. Mais Exemplos de Encapsulamento

Aqui estão mais exemplos práticos e detalhados:

Exemplo 1: Classe Produto

java
public class Produto {
    private String codigo;
    private String nome;
    private double preco;
    private int estoque;
    
    public Produto(String codigo, String nome, double preco, int estoque) {
        this.codigo = codigo;
        setNome(nome);
        setPreco(preco);
        setEstoque(estoque);
    }
    
    // Getters
    public String getCodigo() { return this.codigo; }
    public String getNome() { return this.nome; }
    public double getPreco() { return this.preco; }
    public int getEstoque() { return this.estoque; }
    
    // Setters com validação
    public void setNome(String nome) {
        if (nome != null && !nome.isEmpty()) {
            this.nome = nome;
        } else {
            throw new IllegalArgumentException("Nome não pode ser vazio!");
        }
    }
    
    public void setPreco(double preco) {
        if (preco > 0) {
            this.preco = preco;
        } else {
            throw new IllegalArgumentException("Preço deve ser positivo!");
        }
    }
    
    public void setEstoque(int estoque) {
        if (estoque >= 0) {
            this.estoque = estoque;
        } else {
            throw new IllegalArgumentException("Estoque não pode ser negativo!");
        }
    }
    
    // Métodos de negócio
    public void adicionarEstoque(int quantidade) {
        if (quantidade > 0) {
            this.estoque += quantidade;
            System.out.println(quantidade + " unidades adicionadas!");
        }
    }
    
    public boolean removerEstoque(int quantidade) {
        if (quantidade > 0 && quantidade <= this.estoque) {
            this.estoque -= quantidade;
            System.out.println(quantidade + " unidades removidas!");
            return true;
        }
        System.out.println("Estoque insuficiente!");
        return false;
    }
    
    public double calcularValorTotal() {
        return this.preco * this.estoque;
    }
}

Exemplo 2: Classe Carro

java
public class Carro {
    private String marca;
    private String modelo;
    private int ano;
    private double velocidadeAtual;
    private double velocidadeMaxima;
    private boolean ligado;
    private double combustivel;
    
    public Carro(String marca, String modelo, int ano, double velocidadeMaxima) {
        this.marca = marca;
        this.modelo = modelo;
        this.ano = ano;
        this.velocidadeMaxima = velocidadeMaxima;
        this.velocidadeAtual = 0;
        this.ligado = false;
        this.combustivel = 100;
    }
    
    // Getters
    public String getMarca() { return this.marca; }
    public String getModelo() { return this.modelo; }
    public int getAno() { return this.ano; }
    public double getVelocidadeAtual() { return this.velocidadeAtual; }
    public double getVelocidadeMaxima() { return this.velocidadeMaxima; }
    public boolean isLigado() { return this.ligado; }
    public double getCombustivel() { return this.combustivel; }
    
    // Métodos de operação
    public void ligar() {
        if (!this.ligado && this.combustivel > 0) {
            this.ligado = true;
            System.out.println(this.marca + " " + this.modelo + " foi ligado!");
        }
    }
    
    public void desligar() {
        if (this.ligado && this.velocidadeAtual == 0) {
            this.ligado = false;
            System.out.println("Carro desligado!");
        }
    }
    
    public void acelerar(double incremento) {
        if (!this.ligado) {
            System.out.println("Carro está desligado!");
            return;
        }
        double novaVelocidade = this.velocidadeAtual + incremento;
        if (novaVelocidade <= this.velocidadeMaxima) {
            this.velocidadeAtual = novaVelocidade;
            this.combustivel -= (incremento / 10);
            System.out.println("Acelerou para " + String.format("%.1f", this.velocidadeAtual) + " km/h");
        }
    }
    
    public void abastecerCombustivel(double litros) {
        if (litros > 0) {
            this.combustivel = Math.min(this.combustivel + litros, 100);
            System.out.println("Abastecido com " + litros + " litros!");
        }
    }
}

Exemplo 3: Classe ContaBancaria com Juros

java
public class ContaBancaria {
    private String numero;
    private String titular;
    private double saldo;
    private double taxaJuros;
    
    public ContaBancaria(String numero, String titular, double saldo, double taxaJuros) {
        this.numero = numero;
        setTitular(titular);
        setSaldo(saldo);
        setTaxaJuros(taxaJuros);
    }
    
    // Getters
    public String getNumero() { return this.numero; }
    public String getTitular() { return this.titular; }
    public double getSaldo() { return this.saldo; }
    public double getTaxaJuros() { return this.taxaJuros; }
    
    // Setters com validação
    public void setTitular(String titular) {
        if (titular != null && !titular.trim().isEmpty()) {
            this.titular = titular;
        } else {
            throw new IllegalArgumentException("Titular inválido!");
        }
    }
    
    public void setSaldo(double saldo) {
        if (saldo >= 0) {
            this.saldo = saldo;
        } else {
            throw new IllegalArgumentException("Saldo não pode ser negativo!");
        }
    }
    
    public void setTaxaJuros(double taxa) {
        if (taxa >= 0 && taxa < 100) {
            this.taxaJuros = taxa;
        } else {
            throw new IllegalArgumentException("Taxa de juros inválida!");
        }
    }
    
    // Operações bancárias
    public boolean depositar(double valor) {
        if (valor > 0) {
            this.saldo += valor;
            System.out.println("Depósito de R$ " + String.format("%.2f", valor) + " realizado!");
            return true;
        }
        return false;
    }
    
    public boolean sacar(double valor) {
        if (valor > 0 && valor <= this.saldo) {
            this.saldo -= valor;
            System.out.println("Saque de R$ " + String.format("%.2f", valor) + " realizado!");
            return true;
        }
        return false;
    }
    
    public void aplicarJuros() {
        double jurosCalculados = this.saldo * (this.taxaJuros / 100);
        this.saldo += jurosCalculados;
        System.out.println("Juros de R$ " + String.format("%.2f", jurosCalculados) + " aplicados!");
    }
}

9. Encapsulamento vs Imutabilidade

Encapsulamento permite mudar dados com segurança. Imutabilidade não permite mudar nada depois de criado:

Com Encapsulamento (Mutável)

java
public class Pessoa {
    private String nome;
    
    public Pessoa(String nome) {
        this.nome = nome;
    }
    
    public void setNome(String nome) {
        this.nome = nome;
    }
    
    public String getNome() {
        return this.nome;
    }
}

Com Imutabilidade (Imutável)

java
public final class Pessoa {
    private final String nome;
    
    public Pessoa(String nome) {
        this.nome = nome;
    }
    
    public String getNome() {
        return this.nome;
    }
}

10. Erros Comuns em Encapsulamento

❌ Erro 1: Criar getter/setter para tudo

Nem todo atributo precisa de getter/setter. Se não há necessidade de acessar, deixe privado.

java
// Desnecessário
private int contador;
public int getContador() { return contador; }
public void setContador(int c) { contador = c; }

// Melhor - usar apenas internamente
private int contador;

❌ Erro 2: Setter sem validação

Um setter sem validação é tão perigoso quanto um atributo público.

java
// Ruim
public void setIdade(int idade) {
    this.idade = idade;
}

// Bom
public void setIdade(int idade) {
    if (idade > 0 && idade < 150) {
        this.idade = idade;
    }
}

❌ Erro 3: Retornar arrays sem copiar

Retornar arrays permite que outros os modifiquem. Clone sempre.

java
// Ruim
public int[] getDados() {
    return this.dados;
}

// Bom
public int[] getDados() {
    return this.dados.clone();
}

❌ Erro 4: Atributos públicos

Nunca deixe atributos públicos. Sempre use private e controle via métodos.

java
// Terrível
public class Conta {
    public double saldo;
}

// Correto
public class Conta {
    private double saldo;
}

11. Exercícios Práticos

Exercício 1: Classe Livro

Crie uma classe Livro com atributos: ISBN, título, autor, preço e quantidade em estoque. Implemente getters, setters com validação e métodos para adicionar/remover do estoque.

Exercício 2: Classe Funcionário

Crie uma classe Funcionário com: matrícula, nome, salário base e departamento. Implemente método para calcular salário com bônus (10% se departamento for vendas). Use encapsulamento completo.

Exercício 3: Classe Temperatura

Crie uma classe Temperatura que armazena temperatura em Celsius. Implemente getters para Celsius, Fahrenheit e Kelvin. Use encapsulamento para validar valores.

Exercício 4: Classe Retângulo

Crie uma classe Retângulo com largura e altura privadas. Implemente métodos para calcular área, perímetro e aumentar/diminuir dimensões com validação.

Conclusão

O encapsulamento é o primeiro passo para escrever código profissional e seguro em Java. Proteja seus dados com private, controle acesso com getters/setters e sempre valide seus dados.

Lembre-se: dados privados + métodos públicos = código seguro e flexível!

Próximos passos: Estude os outros pilares - Herança, Polimorfismo e Abstração - para se tornar um mestre em POO!