Модификаторы доступа
Модификаторы доступа контролируют видимость классов, полей и методов. Инкапсуляция начинается здесь — скрывайте данные, показывайте поведение.
Инкапсуляция: зачем скрывать данные
Инкапсуляция — это принцип ООП, который означает: скрывай внутренние данные и предоставляй контролируемый доступ через методы.
Аналогия из жизни: вы нажимаете на педаль газа (публичный интерфейс), а не толкаете поршни напрямую (внутренняя реализация). Автомобиль скрывает сложность двигателя и даёт вам простой интерфейс: руль, педали, кнопки.
Ещё аналогия: банковский счёт. Вы не можете напрямую записать «1 000 000 000 руб.» в ячейку баланса. Вы кладёте деньги через кассу (deposit) или снимаете (withdraw). Банк контролирует операции и проверяет данные.
Зачем это нужно?
- Целостность данных: нельзя задать отрицательный баланс счёта, некорректный возраст или пустое имя. Конструктор и сеттеры проверяют данные.
- Свобода изменений: можно переименовать поле, изменить внутреннюю логику — внешний код не сломается, потому что он обращается к методам, а не к полям напрямую.
- Скрытие сложности: внешний код использует простой интерфейс, не зная внутренних деталей реализации.
- Простота отладки: если данных много — все изменения проходят через методы, где легко поставить breakpoint.
Без инкапсуляции ваш код хрупок: один неверный внешний вызов ломает всю систему. С инкапсуляцией — класс сам защищает свои данные.
Четыре уровня доступа
В Java четыре модификатора доступа, от самого ограниченного к самому открытому:
public class AccessLevels {
private String secret = "Только внутри класса"; // 1. private
String friendly = "Внутри пакета"; // 2. package-private
protected String family = "Пакет + наследники"; // 3. protected
public String open = "Для всех"; // 4. public
}
Представьте дом с четырьмя зонами доступа:
- private — комната с замком. Только вы (класс) имеете ключ.
- package-private — дом без дверей в соседнем доме. Доступно жильцам квартала (пакета).
- protected — дом + гостиная для родственников (подклассы), даже если они живут в другом квартале.
- public — парк. Доступно абсолютно всем.
private — только внутри класса
private — самый строгий модификатор. Поле или метод доступен ТОЛЬКО внутри того же класса. Никто снаружи не может его ни прочитать, ни изменить.
public class BankAccount {
private double balance; // Только этот класс знает баланс
private String pin; // Только этот класс знает PIN
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public boolean checkPin(String inputPin) {
return pin.equals(inputPin);
}
public double getBalance() {
return balance;
}
}
BankAccount acc = new BankAccount();
// acc.balance = 1000000; // Ошибка! balance private
// acc.pin = "1234"; // Ошибка! pin private
acc.deposit(5000); // OK — метод public
System.out.println(acc.getBalance()); // OK — геттер public
Никто не может напрямую менять баланс — только через метод deposit(), который проверяет сумму. Никто не может прочитать PIN — только вызвать checkPin() для проверки.
Приватные методы — это вспомогательные функции, которые используются только внутри класса:
public class Calculator {
public double calculateArea(double radius) {
validateRadius(radius);
return Math.PI * radius * radius;
}
private void validateRadius(double radius) {
if (radius <= 0) {
throw new IllegalArgumentException("Радиус должен быть положительным");
}
}
}
Метод validateRadius private — он нужен только внутри класса Calculator. Внешний код не должен его вызывать.
package-private — доступ в пределах пакета
Если модификатор не указан (нет private, protected, public), это package-private доступ. Поле или метод доступен только классам в ТОМ ЖЕ ПАКЕТЕ.
// Файл: com/company/Car.java
package com.company;
class Car {
String brand; // package-private
int speed; // package-private
void start() { // package-private
System.out.println("Заведён");
}
}
// Файл: com/company/Main.java
package com.company;
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.brand = "Toyota"; // OK — тот же пакет
car.speed = 100; // OK — тот же пакет
car.start(); // OK — тот же пакет
}
}
// Файл: com/other/Helper.java
package com.other;
import com.company.Car;
public class Helper {
void doSomething() {
Car car = new Car();
// car.brand = "BMW"; // Ошибка! Другой пакет
// car.start(); // Ошибка! Другой пакет
}
}
Package-private полезен для вспомогательных классов и методов, которые не должны использоваться за пределами пакета.
protected — пакет + наследники
protected — доступен в пределах пакета И в подклассах (классах-наследниках), даже если они в другом пакете.
// Файл: com/company/Animal.java
package com.company;
public class Animal {
protected String name;
protected int age;
protected void eat() {
System.out.println(name + " ест");
}
}
// Файл: com/other/Dog.java
package com.other;
import com.company.Animal;
public class Dog extends Animal {
void bark() {
System.out.println(name + " гавкает"); // OK — protected доступен
eat(); // OK — protected доступен
}
}
protected используется, когда вы создаёте классы, предназначенные для наследования, и хотите, чтобы наследники имели доступ к некоторым внутренним деталям.
Но в пределах пакета protected и package-private ведут себя одинаково — оба доступны.
public — доступно для всех
public — открытый доступ. Поле, метод или класс доступен отовсюду.
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int multiply(int a, int b) {
return a * b;
}
}
// Где угодно:
Calculator calc = new Calculator();
calc.add(2, 3); // OK
calc.multiply(4, 5); // OK
Публичные методы — это «лицо» класса. Они определяют, что класс умеет делать для внешнего мира. Это API (Application Programming Interface) вашего класса.
Модификаторы доступа для классов
Для верхнеуровневых классов доступны только public и package-private:
// Файл: Helper.java
class Helper { // package-private класс
void doWork() { }
}
// Файл: Service.java
public class Service { // public класс
void process() { }
}
Приватные классы не существуют (для верхнеуровневых). Класс-помощник без модификатора доступен только в своём пакете.
Внутренние классы (inner classes) МОГУТ быть private или protected:
public class Computer {
private String model;
private class Processor {
void process() {
System.out.println("Обработка на " + model);
}
}
public void start() {
Processor p = new Processor();
p.process();
}
}
Практический пример: инкапсулированный класс
Давайте создадим полноценный инкапсулированный класс BankAccount с нуля:
public class Employee {
private String name;
private String position;
private double salary;
private int workHoursPerDay;
public Employee(String name, String position, double salary, int workHoursPerDay) {
setName(name);
setPosition(position);
setSalary(salary);
setWorkHoursPerDay(workHoursPerDay);
}
public String getName() {
return name;
}
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Имя не может быть пустым");
}
this.name = name.trim();
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
if (position == null || position.trim().isEmpty()) {
throw new IllegalArgumentException("Должность не может быть пустой");
}
this.position = position.trim();
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
if (salary < 0) {
throw new IllegalArgumentException("Зарплата не может быть отрицательной");
}
this.salary = salary;
}
public int getWorkHoursPerDay() {
return workHoursPerDay;
}
public void setWorkHoursPerDay(int hours) {
if (hours < 0 || hours > 24) {
throw new IllegalArgumentException("Рабочие часы: 0-24");
}
this.workHoursPerDay = hours;
}
public double getMonthlySalary() {
return salary * 22;
}
public double getHourlyRate() {
if (workHoursPerDay == 0) return 0;
return salary / workHoursPerDay;
}
public String getSummary() {
return String.format("%s (%s) — %.0f руб./мес", name, position, getMonthlySalary());
}
public void displayInfo() {
System.out.println("Сотрудник: " + name);
System.out.println("Должность: " + position);
System.out.printf("Зарплата: %.0f руб./мес%n", salary);
System.out.printf("Часов в день: %d%n", workHoursPerDay);
System.out.printf("Ставка: %.0f руб./час%n", getHourlyRate());
}
}
Employee emp = new Employee("Иванов И.И.", "Программист", 150000, 8);
emp.displayInfo();
System.out.println(emp.getSummary());
Лучшие практики
Вот рекомендации, которых следует придерживаться:
- Поля — всегда
private. Никогда не делайте поля public. - Геттеры/сеттеры —
public. Это интерфейс доступа к данным. - Вспомогательные методы —
private. Они для внутреннего использования. - Публичные методы —
public. Они определяют API класса. - protected — только когда подклассам нужен доступ из других пакетов.
Правило «минимального привилегия»: давайте столько доступа, сколько необходимо, и не больше. Если метод используется только внутри класса — сделайте его private.
Полный пример: класс Rectangle с инкапсуляцией
public class Rectangle {
private double width;
private double height;
public Rectangle(double width, double height) {
setWidth(width);
setHeight(height);
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
if (width <= 0) {
throw new IllegalArgumentException("Ширина должна быть положительной");
}
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
if (height <= 0) {
throw new IllegalArgumentException("Высота должна быть положительной");
}
this.height = height;
}
public double getArea() {
return width * height;
}
public double getPerimeter() {
return 2 * (width + height);
}
public boolean isSquare() {
return width == height;
}
public void display() {
System.out.printf("Прямоугольник: %.1f x %.1f%n", width, height);
System.out.printf(" Площадь: %.1f%n", getArea());
System.out.printf(" Периметр: %.1f%n", getPerimeter());
}
}
Rectangle r = new Rectangle(5, 3);
r.display();
r.setWidth(10);
r.display();
Типичные ошибки
public class Mistakes {
public int count; // Ошибка! Поля должны быть private
private String name; // Правильно
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// Вне класса:
Mistakes m = new Mistakes();
m.count = 10; // ОК (но плохо!) — поле public
m.name = "Иван"; // Ошибка! name private
// Правильно:
m.setName("Иван");
System.out.println(m.getName());
Итоги урока
- 4 модификатора: private < package-private < protected < public.
private— только внутри класса.package-private(без модификатора) — внутри пакета.protected— пакет + подклассы.public— доступно для всех.- Инкапсуляция: скрывайте данные, показывайте поведение.
- Поля всегда private, доступ через геттеры/сеттеры.
- Принцип минимальных привилегий: давайте最小 необходимый доступ.
Следующий урок: static — статические поля и методы, которые принадлежат классу, а не объектам.
Тест: Модификаторы доступа
10 вопросов