Инкапсуляция в Java
Инкапсуляция — один из четырёх принципов ООП. Её суть: внутреннее состояние объекта должно быть скрыто от внешнего мира и доступно только через специально написанные методы. Внешний код работает с объектом как с «чёрным ящиком» — он не должен знать, как именно устроены поля.
Технически инкапсуляция в Java реализуется модификатором private для полей и публичными методами-«акцессорами»: геттерами (getX) и сеттерами (setX). Это даёт два важных преимущества:
Внутри сеттера можно проверить корректность значения — например, не давать выставить угол сервопривода больше 180°.
Можно поменять внутреннее представление без изменения внешнего интерфейса.
Модификаторы доступа
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.