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