$ sudo teach IT
МОДУЛЬ 7 · УРОК 4

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

Модификаторы доступа контролируют видимость классов, полей и методов. Инкапсуляция начинается здесь — скрывайте данные, показывайте поведение.

~30 минут Для новичков Java ООП

Инкапсуляция: зачем скрывать данные

Инкапсуляция — это принцип ООП, который означает: скрывай внутренние данные и предоставляй контролируемый доступ через методы.

Аналогия из жизни: вы нажимаете на педаль газа (публичный интерфейс), а не толкаете поршни напрямую (внутренняя реализация). Автомобиль скрывает сложность двигателя и даёт вам простой интерфейс: руль, педали, кнопки.

Ещё аналогия: банковский счёт. Вы не можете напрямую записать «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 вопросов

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

Premium