Capítulo 05: Programação Orientada a Objetos: Classes, Construtores, Sobrecarga e Encapsulamento
Especialização em Sistemas com Java 21 LTS • Módulo de Modelagem Orientada a Objetos
🗺️ Mapa Conceitual do Tópico
classDiagram
class ContaBancaria {
-String numeroConta
-String titular
-BigDecimal saldo
-boolean bloqueada
+ContaBancaria(numeroConta, titular)
+ContaBancaria(numeroConta, titular, saldoInicial)
+criarContaUniversitaria(titular) ContaBancaria$
+getNumeroConta() String
+getTitular() String
+getSaldo() BigDecimal
+isBloqueada() boolean
+depositar(BigDecimal valor) void
+sacar(BigDecimal valor) boolean
+bloquear(String motivo) void
}
note for ContaBancaria "Invariantes de domínio protegidas:
- Saldo nunca negativo sem limite
- Cópia defensiva em mutações
- this(...) encadeia construtores"
🏛️ 1. O Paradigma de Orientação a Objetos na JVM
No Java, uma Classe é uma planta arquitetural (blueprint) que define um tipo por referência. Quando instanciamos uma classe através do operador new:
- Alocação de Memória: A JVM reserva espaço na memória Heap para armazenar o cabeçalho do objeto (mark word + klass pointer) e seus atributos de instância.
- Inicialização de Variáveis: Os campos recebem valores padrão binários (
0,0.0,false,null). - Execução de Inicializadores: Blocos de inicialização de instância
{ ... }e atribuições inline são executados em ordem. - Chamada ao Construtor: O construtor é acionado, iniciando obrigatoriamente pela chamada explícita ou implícita a
super()(construtor da superclasse). - Retorno do Ponteiro: A referência de memória (endereço na Heap) é retornada e atribuída à variável na Stack.
O Papel da Palavra-Chave this:
- Desambiguação de Escopo: Diferencia parâmetros de métodos dos atributos de instância (
this.titular = titular;). - Encadeamento de Construtores (Constructor Chaining): Redireciona a chamada para outro construtor da mesma classe (
this(numeroConta, titular, BigDecimal.ZERO);), evitando duplicidade de regras de validação.
🔒 2. Encapsulamento e Modificadores de Acesso
O princípio de Encapsulamento determina que o estado interno de um objeto deve ser inacessível diretamente por entidades externas, sendo manipulado unicamente através de métodos públicos que apliquem regras de negócio e preservem os invariantes de domínio.
Tabela Canônica dos 4 Níveis de Acesso:
| Modificador | Mesma Classe | Mesmo Pacote | Subclasse em Outro Pacote | Qualquer Pacote |
|---|---|---|---|---|
private |
Sim | Não | Não | Não |
| (default / package-private) | Sim | Sim | Não | Não |
protected |
Sim | Sim | Sim (via herança) | Não |
public |
Sim | Sim | Sim | Sim |
Proteção Contra Corrupção de Estado: Cópias Defensivas
Ao expor atributos mutáveis (como datas antigas java.util.Date ou coleções List), o retorno direto da referência permite que o chamador modifique o estado interno do objeto sem passar pelos métodos de validação. A solução é retornar cópias defensivas ou invólucros imutáveis (Collections.unmodifiableList(this.historico)).
🏗️ 3. Construtores, Sobrecarga (Overloading) e Imutabilidade
A sobrecarga de construtores permite fornecer múltiplos pontos de entrada para criar um objeto, flexibilizando a inicialização sem comprometer a integridade:
public class Produto {
private final String sku;
private String descricao;
private BigDecimal preco;
// Construtor principal (Telescoping Constructor)
public Produto(String sku, String descricao, BigDecimal preco) {
if (sku == null || sku.isBlank()) throw new IllegalArgumentException("SKU obrigatório.");
this.sku = sku;
this.descricao = descricao;
this.preco = preco;
}
// Sobrecarga secundária reaproveitando o construtor principal
public Produto(String sku, String descricao) {
this(sku, descricao, BigDecimal.ZERO);
}
}
O Modificador final em Atributos:
Atributos declarados como final devem ser inicializados na declaração ou obrigatoriamente dentro de todos os construtores da classe. Uma vez atribuídos, a referência não pode ser reatribuída, promovendo integridade de dados e segurança concorrente.
⚡ 4. Membros Estáticos (static) e Métodos de Fábrica (Static Factory Methods)
Membros anotados com static pertencem à classe e não a uma instância específica. São carregados na área de metadados da JVM (Metaspace) no momento em que a classe é carregada pelo ClassLoader.
Vantagens dos Static Factory Methods sobre Construtores:
- Possuem nomes descritivos (ex:
ContaBancaria.criarContaUniversitaria(...)vs múltiplos construtores com as mesmas assinaturas de tipos). - Não são obrigados a instanciar um novo objeto a cada chamada (podem reutilizar instâncias cacheadas).
- Podem retornar qualquer subtipo do tipo de retorno declarado.
💻 5. Código de Demonstração Corporativo (Java 21 LTS)
package com.empresa.dominio;
import java.math.BigDecimal;
import java.util.Objects;
/**
* Modelo corporativo de Conta Corrente com encapsulamento rigoroso e validações de invariantes.
*/
public class ContaCorrenteCorporativa {
private final String numeroConta;
private final String agencia;
private String titular;
private BigDecimal saldo;
private BigDecimal limiteChequeEspecial;
private boolean bloqueada;
// Construtor Canônico Completo
public ContaCorrenteCorporativa(String numeroConta, String agencia, String titular,
BigDecimal saldoInicial, BigDecimal limiteChequeEspecial) {
this.numeroConta = Objects.requireNonNull(numeroConta, "Número de conta não pode ser nulo.");
this.agencia = Objects.requireNonNull(agencia, "Agência não pode ser nula.");
setTitular(titular); // Validação encapsulada
this.saldo = (saldoInicial != null) ? saldoInicial : BigDecimal.ZERO;
this.limiteChequeEspecial = (limiteChequeEspecial != null) ? limiteChequeEspecial : BigDecimal.ZERO;
this.bloqueada = false;
}
// Sobrecarga com valores padrão
public ContaCorrenteCorporativa(String numeroConta, String agencia, String titular) {
this(numeroConta, agencia, titular, BigDecimal.ZERO, BigDecimal.ZERO);
}
// Static Factory Method
public static ContaCorrenteCorporativa abrirContaEmpresarial(String numeroConta, String agencia, String razaoSocial) {
return new ContaCorrenteCorporativa(numeroConta, agencia, razaoSocial, BigDecimal.ZERO, new BigDecimal("10000.00"));
}
public void depositar(BigDecimal valor) {
if (bloqueada) throw new IllegalStateException("Conta bloqueada para depósitos.");
if (valor == null || valor.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Valor de depósito deve ser positivo.");
}
this.saldo = this.saldo.add(valor);
}
public boolean sacar(BigDecimal valor) {
if (bloqueada) throw new IllegalStateException("Conta bloqueada para saques.");
if (valor == null || valor.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Valor de saque deve ser positivo.");
}
BigDecimal fundosDisponiveis = this.saldo.add(this.limiteChequeEspecial);
if (valor.compareTo(fundosDisponiveis) > 0) {
return false; // Saldo e limite insuficientes
}
this.saldo = this.saldo.subtract(valor);
return true;
}
public void setTitular(String titular) {
if (titular == null || titular.trim().length() < 3) {
throw new IllegalArgumentException("Nome do titular deve conter no mínimo 3 caracteres.");
}
this.titular = titular.trim();
}
public String getNumeroConta() { return numeroConta; }
public String getAgencia() { return agencia; }
public String getTitular() { return titular; }
public BigDecimal getSaldo() { return saldo; }
public BigDecimal getLimiteChequeEspecial() { return limiteChequeEspecial; }
public boolean isBloqueada() { return bloqueada; }
}