Идея
Инкапсуляция скрывает внутреннее состояние объекта и дает доступ к нему только через методы. Так класс сам следит за корректностью своих данных.
Модификаторы доступа
| Модификатор | Класс | Пакет | Наследники | Все |
|---|---|---|---|---|
private | да | нет | нет | нет |
| без модификатора | да | да | нет | нет |
protected | да | да | да | нет |
public | да | да | да | да |
Правило: поля делайте private, наружу открывайте только то, что нужно.
Пример: банковский счет
Без инкапсуляции:
public class BadAccount { public double balance;}Любой код может записать balance = -1000000.
С инкапсуляцией:
public class BankAccount { private double balance; private final String owner; public BankAccount(String owner, double initial) { if (initial < 0) { throw new IllegalArgumentException("Начальный баланс не может быть отрицательным"); } this.owner = owner; this.balance = initial; } public void deposit(double amount) { if (amount <= 0) { throw new IllegalArgumentException("Сумма должна быть положительной"); } balance += amount; } public void withdraw(double amount) { if (amount <= 0) { throw new IllegalArgumentException("Сумма должна быть положительной"); } if (amount > balance) { throw new IllegalStateException("Недостаточно средств"); } balance -= amount; } public double getBalance() { return balance; } public String getOwner() { return owner; }}public class Main { public static void main(String[] args) { BankAccount account = new BankAccount("Анна", 100); account.deposit(50); account.withdraw(30); System.out.println(account.getBalance()); account.withdraw(1000); }}Последняя строка бросит IllegalStateException.
Геттеры и сеттеры
Стандартные имена: getX, setX, для boolean — isX.
public class User { private String name; private int age; private boolean active; public String getName() { return name; } public void setName(String name) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("Имя не может быть пустым"); } this.name = name; } public int getAge() { return age; } public void setAge(int age) { if (age < 0 || age > 150) { throw new IllegalArgumentException("Недопустимый возраст: " + age); } this.age = age; } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; }}Не создавайте сеттер для каждого поля автоматически. Если значение не должно меняться извне, сеттер не нужен.
Неизменяемые объекты
Объект, состояние которого нельзя изменить после создания, безопасен и прост в использовании. Признаки:
- класс
final; - все поля
private final; - нет сеттеров;
- изменяемые поля копируются при входе и выходе.
import java.util.ArrayList;import java.util.Collections;import java.util.List;public final class Order { private final int id; private final List<String> items; public Order(int id, List<String> items) { this.id = id; this.items = new ArrayList<>(items); } public int getId() { return id; } public List<String> getItems() { return Collections.unmodifiableList(items); }}Так внешний код не сможет изменить список внутри заказа. String, Integer, LocalDate — тоже неизменяемые классы.
Защитное копирование
Если метод возвращает изменяемый объект, вернувшийся объект нужно скопировать:
import java.util.Arrays;public class Scores { private final int[] values; public Scores(int[] values) { this.values = values.clone(); } public int[] getValues() { return values.clone(); } public int sum() { return Arrays.stream(values).sum(); }}Вложенные и внутренние классы
Классы можно объявлять внутри других, чтобы скрыть детали реализации:
public class LinkedStack { private static class Node { int value; Node next; Node(int value, Node next) { this.value = value; this.next = next; } } private Node head; private int size; public void push(int value) { head = new Node(value, head); size++; } public int pop() { if (head == null) { throw new IllegalStateException("Стек пуст"); } int value = head.value; head = head.next; size--; return value; } public int size() { return size; } public static void main(String[] args) { LinkedStack stack = new LinkedStack(); stack.push(1); stack.push(2); stack.push(3); System.out.println(stack.pop()); System.out.println(stack.size()); }}Класс Node недоступен снаружи, интерфейс стека остался простым.
