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

Инкапсуляция — один из четырёх принципов ООП. Её суть: внутреннее состояние объекта должно быть скрыто от внешнего мира и доступно только через специально написанные методы. Внешний код работает с объектом как с «чёрным ящиком» — он не должен знать, как именно устроены поля.

Технически инкапсуляция в Java реализуется модификатором private для полей и публичными методами-«акцессорами»: геттерами (getX) и сеттерами (setX). Это даёт два важных преимущества:

  1. Внутри сеттера можно проверить корректность значения — например, не давать выставить угол сервопривода больше 180°.

  2. Можно поменять внутреннее представление без изменения внешнего интерфейса.

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

public    — доступ отовсюду
protected — внутри пакета и в подклассах
(default) — только внутри пакета
private   — только внутри класса

Для инкапсуляции поля делают private, методы-акцессоры — public.

Пример 1. Поле без инкапсуляции (плохо)

public class Servo {
    public int angle;     // открыто всем

    public static void main(String[] args) {
        Servo s = new Servo();
        s.angle = 500;    // нет проверки — состояние повреждено
        System.out.println("angle=" + s.angle);
    }
}

Output:

angle=500

Пример 2. То же поле с инкапсуляцией

public class Servo {
    private int angle;

    public int getAngle() {
        return angle;
    }

    public void setAngle(int angle) {
        if (angle < 0) angle = 0;
        if (angle > 180) angle = 180;
        this.angle = angle;
    }

    public static void main(String[] args) {
        Servo s = new Servo();
        s.setAngle(500);
        System.out.println("angle=" + s.getAngle());
        s.setAngle(-10);
        System.out.println("angle=" + s.getAngle());
        s.setAngle(90);
        System.out.println("angle=" + s.getAngle());
    }
}

Output:

angle=180
angle=0
angle=90

Пример 3. Только чтение (read-only)

public class Sensor {
    private String type;
    private int id;

    public Sensor(String type, int id) {
        this.type = type;
        this.id = id;
    }

    public String getType() { return type; }
    public int getId()      { return id; }

    public static void main(String[] args) {
        Sensor s = new Sensor("DHT22", 1);
        System.out.println(s.getType() + "#" + s.getId());
        // s.id = 2; // ошибка компиляции — id private и без сеттера
    }
}

Output:

DHT22#1

Пример 4. Внутренний инвариант

public class Battery {
    private int percent;

    public Battery(int percent) {
        setPercent(percent);
    }

    public int getPercent() {
        return percent;
    }

    public void setPercent(int percent) {
        if (percent < 0) percent = 0;
        if (percent > 100) percent = 100;
        this.percent = percent;
    }

    public void drain(int amount) {
        setPercent(this.percent - amount);
    }

    public static void main(String[] args) {
        Battery b = new Battery(80);
        b.drain(30);
        System.out.println("p=" + b.getPercent());
        b.drain(70);
        System.out.println("p=" + b.getPercent());
    }
}

Output:

p=50
p=0

Пример 5. Скрытие реализации

public class Temperature {
    private double celsius;

    public void setCelsius(double c) { this.celsius = c; }
    public double getCelsius()       { return celsius; }
    public double getFahrenheit()    { return celsius * 9.0 / 5.0 + 32.0; }
    public double getKelvin()        { return celsius + 273.15; }

    public static void main(String[] args) {
        Temperature t = new Temperature();
        t.setCelsius(25);
        System.out.println("C=" + t.getCelsius());
        System.out.println("F=" + t.getFahrenheit());
        System.out.println("K=" + t.getKelvin());
    }
}

Output:

C=25.0
F=77.0
K=298.15

Пример 6. Приватный метод-помощник

public class Motor {
    private int speed;

    public void setSpeed(int speed) {
        this.speed = clamp(speed, 0, 255);
    }

    public int getSpeed() {
        return speed;
    }

    private int clamp(int v, int min, int max) {
        if (v < min) return min;
        if (v > max) return max;
        return v;
    }

    public static void main(String[] args) {
        Motor m = new Motor();
        m.setSpeed(400);
        System.out.println("speed=" + m.getSpeed());
    }
}

Output:

speed=255

Подводные камни

  • Простое заворачивание public поля в getX/setX без всякой проверки — это не настоящая инкапсуляция, а лишь её видимость.

  • private-поле может быть изменено любым методом этого же класса — следите, чтобы все «выходы» состояния шли через set.

  • Если возвращаете изменяемый объект (например, массив), помните: вызывающий может его изменить. При необходимости возвращайте копию.

  • Не превращайте каждое поле автоматически в getX/setX — иногда лучше предоставить более крупные методы поведения (drain(amount) вместо setPercent).

  • Имена геттеров/сеттеров — соглашение JavaBeans: getName, setName, для boolean обычно isReady / setReady.

См. также

Примечание

Лицензия и источники

Концепция описана на основе официального Java Tutorial от Oracle (https://docs.oracle.com/javase/tutorial/), Oracle Free Documentation License. Перевод, примеры и пояснения — © AlashEd Wiki.