Перейти к содержимому
Теория · Классы и объекты · урок 5 из 10

Методы и инкапсуляция

Представьте, что в ваш дом может зайти любой прохожий и переставить мебель. Обычно так не бывает: гость звонит в дверь, а хозяин сам решает, что ему отдать и что разрешить. У объекта тоже должна быть такая дверь. Посмотрим на банковский счёт, у которого её пока нет:

JAVA
public class Main {
    public static void main(String[] args) {
        Account acc = new Account();
        acc.deposit(100);
        System.out.println(acc.balance); // 100
    }
}

class Account {
    int balance;
    void deposit(int amount) {
        balance += amount;
    }
}

Давайте разберём программу по шагам:

  1. Класс Account описывает счёт с одним полем balance.
  2. Метод deposit объявлен без static. Его вызывают через точку у конкретного счёта: acc.deposit(100).
  3. Внутри deposit имя balance означает поле того счёта, у которого вызвали метод.
  4. println печатает 100.

Пока всё работает. Но поле balance открыто, и записать в него можно что угодно:

JAVA
acc.balance = -500;
System.out.println(acc.balance); // -500

На счёте минус 500, хотя денег с него никто не снимал. Метод deposit мог бы проверять сумму, но код снаружи обходит его стороной и пишет прямо в поле.

Методы объекта

До сих пор вы писали только static-методы и вызывали их из main по имени. Метод без static принадлежит объекту — это действие конкретного дома, а не чертежа. Такие методы вы уже вызывали у строк: word.length(). Что важно знать о таком методе:

  • его вызывают через объект: acc.deposit(100);
  • внутри него есть this — ссылка на объект, у которого метод вызвали;
  • имя поля без this. означает поле этого объекта: balance в deposit — это this.balance;
  • у двух счетов свои поля, поэтому a.deposit(100) не меняет счёт b.

Слово private

Закроем поле от чужого кода. Модификатор доступа — слово перед полем или методом, которое решает, какой код может к нему обратиться. Модификатор private открывает поле только коду внутри его класса: private int balance;. Теперь строка acc.balance = -500; в main не компилируется:

TEXT
error: balance has private access in Account

Запрет действует и на чтение: System.out.println(acc.balance) даст ту же ошибку. Граница проходит по классу, а не по файлу: Main и Account лежат в одном файле Main.java, но Main — другой класс. Нарушение ловит компилятор, поэтому до запуска такая программа не доходит.

Геттер и проверка

Если поле закрыто, как узнать баланс? Класс сам открывает то, что нужно, через свои методы:

JAVA
class Account {
    private int balance;
    int getBalance() {
        return balance;
    }

    void deposit(int amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
}

Разберём, что изменилось:

  1. private int balance; — поле видно только внутри Account.
  2. getBalance() возвращает значение поля. Такой метод называют геттером, и его имя по традиции начинают с get.
  3. deposit проверяет сумму: отрицательный или нулевой вклад баланс не меняет.
  4. Метода, который записал бы в balance любое число, нет, и так сделано нарочно.

Метод, который записывает в поле новое значение, называют сеттером и начинают его имя с set: setOwner(String owner). Но сеттер, который принимает что угодно, по сути открывает поле.

Закрытые поля и открытые операции, которые соблюдают правила объекта, вместе называют инкапсуляцией. Счёт сам решает, какой вклад принять, и минус на нём не появится. На собеседовании инкапсуляцию часто просят объяснить на примере. Хороший ответ — счёт с deposit: дело не в паре геттер и сеттер на каждое поле, а в том, что объект защищает свои данные.

Здесь картинка с дверью неточна. Дверь у каждого дома своя, а private закрывает поле от других классов, а не от других объектов. Метод класса Account может прочитать balance другого счёта, если получит ссылку на него.

Четыре уровня доступа

В Java четыре уровня доступа, от самого закрытого к самому открытому:

  • private — только код внутри своего класса;
  • без модификатора — классы того же пакета, то есть той же группы классов. Все классы программы из одного файла лежат в одном пакете, и поэтому такие поля до сих пор видел любой ваш код;
  • protected — то же, что без модификатора, плюс классы-наследники из других пакетов;
  • public — любой код, как у класса Main и метода main.

Частый вопрос на собеседовании: что значит поле или метод без модификатора? Он не public: его видят только классы своего пакета.

Частая ошибка

Метод объекта вызвали через имя класса:

JAVA
Account.deposit(100); // ✗
TEXT
error: non-static method deposit(int) cannot be referenced from a static context

Программа не компилируется. deposit меняет баланс конкретного счёта, а запись Account.deposit(100) не говорит, какого. Сначала создайте объект, а потом вызывайте метод у него: Account acc = new Account();, затем acc.deposit(100);.

Главное

  • Метод без static принадлежит объекту: его вызывают через объект, а balance внутри него — поле этого объекта.
  • private закрывает поле от всех других классов, даже в том же файле: acc.balance из main не компилируется.
  • Инкапсуляция — закрытые поля и открытые операции, которые соблюдают правила объекта: геттер и deposit с проверкой.
  • Уровни доступа от закрытого к открытому: private, без модификатора (пакет), protected, public.