Статьи

Полный курс по языку Java — от установки JDK до многопоточности и HTTP-запросов

Инкапсуляция

Страница 15 из 30 · 2026-10-25 · 4 мин чтения

Идея

Инкапсуляция скрывает внутреннее состояние объекта и дает доступ к нему только через методы. Так класс сам следит за корректностью своих данных.

Модификаторы доступа

МодификаторКлассПакетНаследникиВсе
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 недоступен снаружи, интерфейс стека остался простым.