Методы и инкапсуляция
Представьте, что в ваш дом может зайти любой прохожий и переставить мебель. Обычно так не бывает: гость звонит в дверь, а хозяин сам решает, что ему отдать и что разрешить. У объекта тоже должна быть такая дверь. Посмотрим на банковский счёт, у которого её пока нет:
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;
}
}Давайте разберём программу по шагам:
- Класс
Accountописывает счёт с одним полемbalance. - Метод
depositобъявлен безstatic. Его вызывают через точку у конкретного счёта:acc.deposit(100). - Внутри
depositимяbalanceозначает поле того счёта, у которого вызвали метод. printlnпечатает100.
Пока всё работает. Но поле balance открыто, и записать в него можно что угодно:
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 не компилируется:
error: balance has private access in AccountЗапрет действует и на чтение: System.out.println(acc.balance) даст ту же ошибку. Граница проходит по классу, а не по файлу: Main и Account лежат в одном файле Main.java, но Main — другой класс. Нарушение ловит компилятор, поэтому до запуска такая программа не доходит.
Геттер и проверка
Если поле закрыто, как узнать баланс? Класс сам открывает то, что нужно, через свои методы:
class Account {
private int balance;
int getBalance() {
return balance;
}
void deposit(int amount) {
if (amount > 0) {
balance += amount;
}
}
}Разберём, что изменилось:
private int balance;— поле видно только внутриAccount.getBalance()возвращает значение поля. Такой метод называют геттером, и его имя по традиции начинают сget.depositпроверяет сумму: отрицательный или нулевой вклад баланс не меняет.- Метода, который записал бы в
balanceлюбое число, нет, и так сделано нарочно.
Метод, который записывает в поле новое значение, называют сеттером и начинают его имя с set: setOwner(String owner). Но сеттер, который принимает что угодно, по сути открывает поле.
Закрытые поля и открытые операции, которые соблюдают правила объекта, вместе называют инкапсуляцией. Счёт сам решает, какой вклад принять, и минус на нём не появится. На собеседовании инкапсуляцию часто просят объяснить на примере. Хороший ответ — счёт с deposit: дело не в паре геттер и сеттер на каждое поле, а в том, что объект защищает свои данные.
Здесь картинка с дверью неточна. Дверь у каждого дома своя, а private закрывает поле от других классов, а не от других объектов. Метод класса Account может прочитать balance другого счёта, если получит ссылку на него.
Четыре уровня доступа
В Java четыре уровня доступа, от самого закрытого к самому открытому:
private— только код внутри своего класса;- без модификатора — классы того же пакета, то есть той же группы классов. Все классы программы из одного файла лежат в одном пакете, и поэтому такие поля до сих пор видел любой ваш код;
protected— то же, что без модификатора, плюс классы-наследники из других пакетов;public— любой код, как у классаMainи методаmain.
Частый вопрос на собеседовании: что значит поле или метод без модификатора? Он не public: его видят только классы своего пакета.
Частая ошибка
Метод объекта вызвали через имя класса:
Account.deposit(100); // ✗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.