08 — Encapsulamento e Modificadores de Acesso
Aprenda a como proteger e controlar o acesso aos dados das suas classes.
O Encapsulamento consiste em ocultar os detalhes internos de uma classe e determinar como os seus dados podem ser acessados ou modificados. Isso evita que erros aconteçam e o seu código se torna mais simples de manter e atualizar.
O problema sem o encapsulamento
public class Player {
public String name;
public int health;
public int maxHealth;
}
public class Main {
public static void main(String[] args) {
Player player = new Player();
player.health = 100;
player.maxHealth = 100;
// Ops! Alguém pode quebrar as regras
player.health = 500; // Vida excedente!
player.health = -50; // Vida negativa!
player.name = ""; // Sem nome!
}
}Sem proteção, qualquer um pode definir valores inválidos!
Modificadores de Acesso
O Java tem palavras-chave que controlam quem pode acessar os membros da sua classe:
| Modificador | Classe | Pacote | Subclasse | Mundo |
|---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | ✗ |
| (nenhum) | ✓ | ✓ | ✗ | ✗ |
private | ✓ | ✗ | ✗ | ✗ |
Por enquanto, concentre-se em:
public— Qualquer pessoa pode acessarprivate— Apenas essa classe pode acessar
Tornando Propriedades Privadas
public class Player {
private String name;
private int health;
private int maxHealth;
public Player(String name, int maxHealth) {
this.name = name;
this.health = maxHealth;
this.maxHealth = maxHealth;
}
}Você não consegue mais acessar as propriedades diretamente:
Player player = new Player("Alice", 100);
player.health = 500; // ❌ Erro! Health é privadoMétodos Getters e Setters
Para acessar propriedades privadas, crie os métodos getter e setter:
public class Player {
private String name;
private int health;
private int maxHealth;
public Player(String name, int maxHealth) {
this.name = name;
this.health = maxHealth;
this.maxHealth = maxHealth;
}
// Getter – retorna o valor
public int getHealth() {
return health;
}
// Setter – define o valor com validação
public void setHealth(int health) {
if (health < 0) {
this.health = 0;
} else if (health > maxHealth) {
this.health = maxHealth;
} else {
this.health = health;
}
}
public String getName() {
return name;
}
public int getMaxHealth() {
return maxHealth;
}
}Agora você pode interagir com segurança com o objeto:
Player player = new Player("Alice", 100);
player.setHealth(150); // Limitado automaticamente a 100
System.out.println(player.getHealth()); // 100
player.setHealth(-20); // Definido automaticamente para 0
System.out.println(player.getHealth()); // 0Seguir convenções de nomenclatura do Java:
- Getter:
get+ nome da propriedade (capitalizado); - Setter:
set+ nome da propriedade (capitalizado); - Boolean:
is+ nome da propriedade (capitalizado)
private int health;
public int getHealth() { }
public void setHealth(int health) { }
private boolean alive;
public boolean isAlive() { }
public void setAlive(boolean alive) { }
private String name;
public String getName() { }
public void setName(String name) { }Vantagens do Encapsulamento
1. Validação
public class Item {
private int durability;
private int maxDurability;
public void setDurability(int durability) {
if (durability < 0) {
this.durability = 0;
} else if (durability > maxDurability) {
this.durability = maxDurability;
} else {
this.durability = durability;
}
}
public boolean isBroken() {
return durability <= 0;
}
}2. Propriedades Apenas de Leitura
Às vezes, você não quer um setter:
public class Monster {
private String id; // Deve permanecer inalterável
private int health;
public Monster(String id, int health) {
this.id = id;
this.health = health;
}
// Apenas getter, sem setter!
public String getId() {
return id;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
}3. Propriedades calculadas
Os métodos Getters não precisam retornar um campo diretamente:
public class Player {
private int health;
private int maxHealth;
public int getHealth() {
return health;
}
// Propriedade calculada
public double getHealthPercentage() {
return (health * 100.0) / maxHealth;
}
// Propriedade calculada
public boolean isLowHealth() {
return getHealthPercentage() < 25;
}
}Exemplos práticos
Item com durabilidade
public class Tool {
private String name;
private int durability;
private int maxDurability;
private boolean broken;
public Tool(String name, int maxDurability) {
this.name = name;
this.durability = maxDurability;
this.maxDurability = maxDurability;
this.broken = false;
}
public void use() {
if (broken) {
System.out.println(name + " is broken!");
return;
}
durability--;
System.out.println(name + " used. Durability: " + durability);
if (durability <= 0) {
broken = true;
System.out.println(name + " broke!");
}
}
public void repair() {
durability = maxDurability;
broken = false;
System.out.println(name + " repaired!");
}
// Getters
public String getName() {
return name;
}
public int getDurability() {
return durability;
}
public boolean isBroken() {
return broken;
}
public double getDurabilityPercentage() {
return (durability * 100.0) / maxDurability;
}
}Exemplo de conta bancária
private int gold;
private int silver;
public PlayerWallet() {
this.gold = 0;
this.silver = 0;
}
public void addGold(int amount) {
if (amount > 0) {
gold += amount;
System.out.println("Added " + amount + " gold");
}
}
public boolean spendGold(int amount) {
if (amount > gold) {
System.out.println("Not enough gold!");
return false;
}
gold -= amount;
System.out.println("Spent " + amount + " gold");
return true;
}
public int getGold() {
return gold;
}
public int getTotalValue() {
// 1 gold = 100 silver
return gold * 100 + silver;
}
}Sistema de proteção de bloco
public class ProtectedBlock {
private int x, y, z;
private String type;
private String owner;
private boolean locked;
public ProtectedBlock(int x, int y, int z, String type, String owner) {
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.owner = owner;
this.locked = true;
}
public boolean canBreak(String playerName) {
if (!locked) {
return true;
}
return playerName.equals(owner);
}
public void unlock(String playerName) {
if (playerName.equals(owner)) {
locked = false;
System.out.println("Block unlocked");
} else {
System.out.println("You don't own this block!");
}
}
// Somente Getters - posição e owner não devem mudar
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public String getOwner() {
return owner;
}
public boolean isLocked() {
return locked;
}
}Quando usar Private e Public
Torná-lo privado por padrão! Apenas torne as coisas públicas se elas precisarem ser acessadas de fora.
Private:
- Dados internos (health, position, inventory);
- Métodos de ajuda usados apenas na classe;
- Tudo que precise de validação.
Public:
- Métodos que definem o comportamento da classe;
- Construtor;
- Métodos que outras classes precisam utilizar.
public class Example {
// Privado - dados internos
private int internalCounter;
private String secretKey;
// Público - parte da interface
public void doSomething() {
// Usa um método auxiliar privado
validateData();
}
// Auxiliar interno privado
private void validateData() {
// ...
}
}A palavra-chave final
final significa que uma variável não pode ser alterada após ser definida:
public class Player {
private final String id; // Deve permanecer inalterado após ser criado
private String name; // Alterável
private int health; // Alterável
public Player(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
// Não há setId() — termina aqui!
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}A palavra-chave static
Membros estáticos
Uma classe pode definir dois tipos de membros:
-
Instance members — pertencentes a cada objeto (cada instância tem sua própria cópia).
-
Static members — pertencentes à classe (uma cópia compartilhada do tipo inteiro).
Simplificando um pouco: os 'Instance Members' pertencem a objetos, enquanto os 'Static Members' pertencem à própria classe e são compartilhados por todos os objetos desse tipo.
Declaração
/* (modificador de acesso) */ static ... memberName; Exemplo
class Data {
public int x; // Instanced member
public static int y = 1000; // Static member
// Membro da Instância (Instanced member):
// Pode acessar ambos os membros, estáticos e instanciados
public void foo() {
x = 100; // OK - same as this.x = 100;
y = 100; // OK - same as Data.y = 200;
}
// Membro Estático (Static member):
// Não pode acessar variáveis de membros não-estáticos
public static void bar() {
x = 100; // Error: non-static variable x cannot be renference from a static context
y = 100; // OK
}
}Acessando Membros Estáticos (Static Members)
Data data = new Data();
data.x = 1000; // OK
data.y = 1000; // OK-ish - não é realmente recomendado; é melhor usar Data.y
Data.y = 1000; // OK - boa prática
Data.x = 1000; // Erro: não é possível acessar variáveis de instância em um contexto estáticoCampos estáticos
Um campo estático representa um dado membro de propriedade do tipo de classe, em vez do objeto. Campos estáticos também são armazenados em um local específico de memória que foi compartilhado entre todas as instâncias de objeto que são criadas.
É declarado o seguinte:
/* (modificador de acesso) (opcional) */ static /* final/volatile (opcional) */ fieldName;Vamos pegar o mesmo exemplo da Classe de Dados e adicionar este construtor:
public Data() {
y++; // lembre-se de que isso é o mesmo que Data.y++;
}// Cada instância de Data terá uma cópia privada do membro de instância x
//No entanto, todas apontarão para o mesmo local na memória para o membro y
Data d1 = new Data(); // y = 1001
d1.x = 5;
Data d2 = new Data(); // y = 1002
d2.x = 25;
Data d3 = new Data(); // y = 1003
// ... e assim por dianteMétodos estáticos
Métodos static essencialmente representam uma função membro de um determinado tipo de classe
Da classe Data, lembre-se da função (instanced method) foo e do (static method) bar
É possível acessar esses métodos via:
Data d1 = new Data();
d1.foo(); // Instanced method: acessível SOMENTE por um objeto
Data.bar(); // Static method: acessível sem um objetoInicializador estático
Use a static initializer block to run initialization logic when the class is first loaded:
class OtherData {
private static int a = 12;
private static int b;
private static String msg;
static {
msg = "Initialization..."
System.out.println(msg);
b = 4;
// ... inicialização complexa que não pode ser feita em uma única expressão
}
}Exercícios práticos
-
Crie uma classe
BankAccount:- Propriedades privadas:
accountNumber,balance; - Construtor para definir o número da conta;
- Métodos:
deposit(),withdraw(),getBalance(); - Validação: não é possível sacar mais do que o saldo;
- O número da conta deve ser apenas leitura.
- Propriedades privadas:
-
Crie uma classe
Door:- Propriedades privadas:
isLocked,keyCode; - Construtor para definir o código-chave;
- Métodos:
lock(),unlock(String code),isLocked(); - O método
unlock()funciona apenas com o código correto; - O código deve ser
private(não o exponha!).
- Propriedades privadas:
-
Crie uma classe
PlayerStats:- Propriedades privadas:
strength,defense,speed; - Construtor para definir todas as estatísticas;
- Getters para todas as estatísticas;
- Método:
getPowerLevel()que retornastrength+defense+speed; - As estatísticas não podem ser negativas ou superiores a 100.
- Propriedades privadas:
-
Refatore uma Classe: Pegue uma de suas classes da lição anterior e adicione o encapsulamento adequado:
- Prive todas as propriedades;
- Adicionar
gettersesettersapropriados; - Adicione a validação quando for necessário.