W3docs

Encapsulamento em Java

Agrupe dados e métodos em classes Java e oculte detalhes de implementação usando campos privados e acessores públicos.

Encapsulamento é o primeiro dos quatro pilares da OOP, e o mais fácil de colocar em prática: mantenha os dados de uma classe como privados e exponha o comportamento através de métodos. A classe torna-se responsável pelo seu próprio estado — nenhum código externo pode colocá-la em um estado inválido — e o restante do programa interage com ela por meio de uma interface que você controla.

O mecanismo são campos private mais métodos public. A disciplina é "não exponha estado que você não pode controlar."

O padrão

Uma classe não encapsulada é apenas um conjunto de campos públicos:

public class Account {
  public int balance;
}

Account a = new Account();
a.balance = 100;
a.balance = -50;   // nothing stops this

A versão encapsulada oculta o campo e expõe operações deliberadas:

public class Account {
  private int balance;

  public int  balance()              { return balance; }
  public void deposit(int amount) {
    if (amount <= 0) throw new IllegalArgumentException();
    balance += amount;
  }
  public boolean withdraw(int amount) {
    if (amount <= 0) throw new IllegalArgumentException();
    if (amount > balance) return false;
    balance -= amount;
    return true;
  }
}

Agora não há como o código externo definir balance com um número negativo, sobrescrevê-lo sem passar pela validação, ou verificá-lo sem passar por balance().

O que você ganha

Invariantes em que você pode confiar. A classe Account garante que "balance ≥ 0 após cada operação pública." Como nada externo pode tocar em balance, a regra pode ser aplicada a partir de um único arquivo, não de toda a base de código.

Liberdade para mudar. Com balance privado, você pode mais tarde alterar seu tipo de int para long, mudar a unidade de dólares para centavos, armazená-lo em um BigDecimal ou movê-lo para um banco de dados — sem modificar um único chamador. Com um campo público, o tipo e o armazenamento ficam incorporados em cada ponto de chamada.

Uma API menor e mais clara. Os chamadores veem apenas deposit, withdraw, balance — não as dezenas de auxiliares privados que os fazem funcionar. A classe anuncia o que faz, não como.

Localidade de raciocínio. Quando algo dá errado com balance, o bug está em um de três métodos, não em qualquer um dos mil lugares que poderiam, de outra forma, escrever no campo.

Encapsulamento ≠ getters e setters para tudo

Um antipadrão comum é gerar mecanicamente um getter e um setter para cada campo:

public class Account {
  private int balance;
  public int  getBalance()           { return balance; }
  public void setBalance(int v)      { this.balance = v; }    // same as public field, with extra steps
}

Isso é tecnicamente "encapsulado" no sentido do livro didático, mas não alcança nenhum dos benefícios reais do encapsulamento — qualquer um ainda pode colocar o objeto em qualquer estado que desejar. O encapsulamento real expressa operações, não acesso bruto ao campo:

  • deposit(amount) em vez de setBalance(balance + amount)
  • withdraw(amount) retornando sucesso/falha em vez de setBalance(balance - amount)
  • balance() (um acessor somente leitura) sem um setBalance correspondente

O próximo capítulo sobre getters e setters aborda as convenções para quando cada um é apropriado.

Cópias defensivas

Se o tipo de um campo é mutável (um array, uma lista, um Date), retorná-lo diretamente vaza o controle:

public class Order {
  private final List<String> items = new ArrayList<>();
  public List<String> items() { return items; }      // leak!
}

Order o = new Order();
o.items().add("apple");                              // outside code mutated the order

A correção é retornar uma visão não modificável ou uma cópia defensiva:

public List<String> items() {
  return List.copyOf(items);          // immutable snapshot
}

O mesmo cuidado se aplica a setters que recebem valores mutáveis — copie-os na entrada:

public Order(List<String> items) {
  this.items = new ArrayList<>(items);
}

O capítulo sobre classes imutáveis aprofunda o assunto.

Escolhendo o nível de acesso correto

private e public são os dois que você usa com mais frequência, mas Java tem quatro níveis, e os intermediários importam para o encapsulamento:

  • private — visível apenas dentro da mesma classe. O padrão para campos e métodos auxiliares.
  • package-private (sem palavra-chave) — visível para outras classes no mesmo pacote. Útil quando algumas classes cooperantes formam uma unidade e devem ver os internos umas das outras, mas o mundo externo não deve.
  • protected — package-private mais visível para subclasses. Reserve para membros que uma subclasse genuinamente precisa sobrescrever ou construir sobre.
  • public — visível em qualquer lugar. Esta é sua API publicada; uma vez que código externo dependa dela, alterá-la quebra os chamadores.

A regra prática é o princípio da menor exposição: dê a cada membro o acesso mais restrito que ainda permita o funcionamento do código, e amplie apenas quando uma necessidade concreta surgir. Um campo ou método public é uma promessa que você precisa cumprir. Consulte o capítulo sobre modificadores de acesso para a tabela completa.

Encapsulamento no design

A partir de um certo tamanho, o encapsulamento torna-se uma ferramenta de design, não apenas uma regra de codificação. Cada classe traça um limite ao redor de um bloco de estado e das operações sobre ele; o restante do sistema interage com ele por meio desse limite. A maioria dos padrões arquiteturais — em camadas, hexagonal, MVC, Clean — são argumentos sobre onde traçar os limites.

Diretrizes práticas:

  • Campos são privados até que se prove o contrário. Comece assim; amplie apenas com uma razão.
  • Exponha verbos, não substantivos. cancel(), pay(), ship() são melhores que setStatus(...).
  • Não retorne internos mutáveis diretamente. Envolva, copie ou use uma visão imutável.
  • Valide na entrada. Rejeite estado impossível no limite para que o código interno possa assumir que é válido.

Um exemplo prático

java— editable, runs on the server

O que vem a seguir

A parte mecânica do encapsulamento — os campos private com métodos public que os leem ou escrevem — tem suas próprias convenções que vale a pena conhecer. Continue para getters e setters.

Prática

Prática
Por que gerar mecanicamente um getter público e um setter público para cada campo privado derrota o propósito do encapsulamento?
Por que gerar mecanicamente um getter público e um setter público para cada campo privado derrota o propósito do encapsulamento?
Was this page helpful?