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:

  1. 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.
  2. Inicialização de Variáveis: Os campos recebem valores padrão binários (0, 0.0, false, null).
  3. Execução de Inicializadores: Blocos de inicialização de instância { ... } e atribuições inline são executados em ordem.
  4. Chamada ao Construtor: O construtor é acionado, iniciando obrigatoriamente pela chamada explícita ou implícita a super() (construtor da superclasse).
  5. 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; }
}