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

Инкапсуляция в Java реализована с помощью использования модификаторов доступа.

Язык Java предоставляет несколько уровней защиты, которые позволяет настраивать область видимости данных и методов. В Java имеется четыре категории видимости элементов класса:

  • private– члены класса доступны только членам данного класса. Всё что объявлено private, доступно только конструкторам и методам внутри класса и нигде больше. Они выполняют служебную или вспомогательную роль в пределах класса и их функциональность не предназначена для внешнего пользования. Закрытие (private) полей обеспечивает инкапсуляцию;

  • по умолчанию (package-private) – члены класса доступны классам, которые находятся в этом же пакете;

  • protected– члены класса доступны классам, находящимся в том же пакете, и подклассам – в других пакетах;

  • public– члены класса доступны для всех классов в этом и других пакетах.

Модификатор класса указывается перед остальной частью описания типа отдельного члена класса. Это означает, что именно с него должен начинаться оператор объявления класса.

public String errMessage;
private AccountBalance balance;

private boolean isError(byte status) {}
public class Account {}

Когда член класса обозначается модификатором доступа public, он становится доступным для любого другого кода в программе, включая и методы, определенные в других классах.

Когда член класса обозначается модификатором private, он может быть доступен только другим членам этого класса. Следовательно, методы из других классов не имеют доступа к закрытому члену класса.

При отсутствии модификатора доступа, члены класса доступны другим членам класса, который находится в этом же пакете.

Модификатор доступа protected связан с использованием механизма наследования и будет рассмотрен позже.

Модификатор доступа указывается перед остальной частью описания типа отдельного члена класса (то есть, именно с модификатора доступа начинается объявление члена класса).

Член класса (переменная, конструктор, методы), объявленный public, доступен из любого метода вне класса.

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

Сокрытие полей класса

В подавляющем большинстве случаев, поля класса объявляются как private (это не касается статических переменных и констант, там ситуация может быть другая). Должны быть веские основания объявить поле класса общедоступным. Манипулирование данными должно осуществляться только с помощью методов.

Для того чтобы дать возможность получить доступ к переменной или дать возможность изменить ее значение, объявляют специальные методы, которые называются "геттерами" и "сеттерами".

Геттер возвращает значение приватного поля, тогда как сеттер меняет значение приватного поля (новое значение передается в качестве аргумента метода).

Хотя сигнатура и имена геттеров и сеттеров могут быть любыми, приучите себя соблюдать строгий шаблон для объявления геттеров и сеттеров.

Геттер должен иметь префикс get, после которого идет название поля с большой буквы. Геттер, как правило, не имеет входных аргументов.

Сеттер должен иметь префикс set, после которого идет название поля с большой буквы. Сеттер принимает на вход новое значение поля. Возвращаемый тип, как правило, void.

Account.java
public class Account {

    private double balance;

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

Большинство IDE для Java имеют механизм для генерации геттеров и сеттеров. В IntelliJ IDEA нажмите комбинацию Alt+Insert находясь в окне редактирования java-файла. Откроется контекстное меню Generate, где вы можете выбрать генерацию геттера и сеттера, после чего указать поля, для которых необходимо сгенерировать методы.

5. Пример использования инкапсуляции

Представим, что нам необходимо создать класс «Корзина» (Cart), который хранит в себе набор объектов класса «Товар» (Item).

Какие методы «Корзина» должна предоставлять для внешнего использования? Это могут быть, например, методы «Добавить товар», «Убрать последний добавленный товар», «Подсчет суммы цен товаров в корзине», «Повышение цен в корзине на N процентов» и «Снижение цен в корзине на N процентов».

Название метода

Описание

public Cart(int capacity)

Конструктор с 1 параметром – максимальным количеством товаров в корзине.

public boolean addItem(Item item)

Добавление товара в корзину. Возвращает успешность операции.

public Item deleteLastAddedItem()

Удаление последнего добавленного товара в корзину. Возвращает удаленный товар.

public double calculateItemPrices()

Подсчет суммы цен всех товаров в корзине.

public void raiseItemPrices(double percent)

Поднять цены товаров в корзине на определенный процент (значение процента передается как аргумент метода).

public void cutItemPrices(double percent)

Снизить цены товаров в корзине на определенный процент (значение процента передается как аргумент метода).

Как вы можете заметить, это публичные методы, а значит, их можно вызвать через оператор-точку имея ссылку ну объект.

Cart cart = new Cart();
cart.addItem(new Item("Клавиатура", 2000));

Перечень этих публичных методов и составляет интерфейс класса – то есть, с помощью этих методов объект класса будет взаимодействовать с внешним миром.

Эти методы имеют вполне четко определенные входные аргументы и могут возвращать значения четко определенных типов, и никак иначе. По аналогии с этим, поворот колес автомобиля осуществляется четко определенным образом – поворотом руля, и бензин надо заливать в четко определенное отверстие крышки бензобака, а не как-то еще.

То – как будет реализовано хранение товаров в корзине – это внутренняя логика класса и она не должна быть доступна внешнему миру, она должна быть скрыта от внешнего вмешательства. Другие классы, которые будут использовать объекты класса Cartне должны знать и не должны иметь доступ к тому – как там «внутри» реализовано хранение товаров, подсчет цен и изменение цены на определенный процент и так далее, они могут только лишь использовать предоставленные им публичные методы. Давайте реализуем «Корзину» с помощью структуры «стек», которая, в свою очередь, реализована обычным массивом.

Cart.java
public class Cart {

    private Item[] stack; // массив для реализации стека
    private int topIndex; // указатель на вершину стека

    // При создании корзины мы должны
    // указать максимальное количество элементов
    // в корзине
    public Cart(int capacity) {
        stack = new Item[capacity];
        topIndex = -1;
    }

    // Добавление нового товара в корзину
    public boolean addItem(Item item) {
        return push(item);
    }

    // Приватный метод, который реализует добавление в стек
    private boolean push (Item item) {
        // Добавляем товар в стек
        return true; // или false если не стек переполнен
    }
    
    // Удаление последнего добавленного товара в корзину
    public Item deleteLastAddedItem() {
        return pop();
    }

    // Приватный метод, который реализует извлечение из стека
    private Item pop() {
        return new Item(); // Извлеченный из стека товар
    }
}

Как мы видим, массив с товарами, указать на вершину стек объявлены как privateчлены класса. Это значит, что мы не можем получить к ним доступ извне – они доступны только внутри данного класса.

Cart cart = new Cart();
cart.addItem(new Item("Клавиатура", 2000));

// Данная инструкция вызовет ошибку компиляции
cart.topIndex = 4;

Программиста, который будет использовать класс Cart, не должна волновать ситуация с переполнением стека, с попыткой извлечь элемент из пустого стека, он не должен следить за указателем на вершину стека, он даже не должен знать что это стек.

Для него объект класса Cart это некоторый объект, который предоставляет «услугу» в виде корзины товаров и с этой корзиной можно работать с помощью определенных публичных методов.

В дальнейшем мы можем переделать класс Cart и поменять внутреннюю реализацию. Мы можем использовать структуру "очередь", мы можем использовать коллекции, мы можем иначе реализовать операции добавления и удаления элемента в стеке, но если мы сохраним интерфейс класса неизменным, то для внешнего мира эти изменения внутренней логики не будут важны и если мы поменяем внутреннюю логику одного небольшого участка программы, то вся остальная программа будет работать так же.

Last updated