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

🔥 🚀 Важно для всех, кто работает с Java! 🔥
На JavaRocks ты найдешь уникальные туториалы, практические задачи и редкие книги, которых не найти в свободном доступе. Присоединяйся к нашему Telegram-каналу JavaRocks — стань частью профессионального сообщества!

Из этой статьи вы узнаете, что такое модификаторы доступа в Java, а также познакомитесь с их работой на примерах.

Модификаторы доступа — это чаще всего ключевые слова, которые регулируют доступ к различным частям вашего кода. Почему “чаще всего”? Потому что один из них устанавливается по умолчанию — без использования ключевого слова 🙂

В Java есть четыре модификатора доступа. Вот они — от самого строгого до самого “мягкого”:

  • private;
  • default (доступ внутри пакета);
  • protected;
  • public.

А теперь рассмотрим каждый из них подробнее — когда и зачем использовать, и, конечно, посмотрим примеры 🙂

Модификатор private

Модификатор private

private — самый строгий модификатор доступа в Java. Он позволяет обращаться к данным и методам только внутри одного класса.

Возможно, вы уже встречали их в статье про геттеры и сеттеры. Вот пример:

public class Cat {

   public String name;
   public int age;
   public int weight;

   public Cat(String name, int age, int weight) {
       this.name = name;
       this.age = age;
       this.weight = weight;
   }

   public Cat() {
   }

   public void sayMeow() {
       System.out.println("Meow!");
   }
}

public class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();
       cat.name = "";
       cat.age = -1000;
       cat.weight = 0;
   }
}
Run Code

Здесь допущена серьезная ошибка: поля public допускают прямое изменение их значений из любого внешнего класса. Причем без каких-либо проверок. В результате, наша программа может спокойно создать кота с именем "", возрастом -1000 и весом 0.

Чтобы решить эту проблему, мы использовали геттеры и сеттеры, а также ограничили прямой доступ к полям с помощью модификатора private:

public class Cat {

   private String name;
   private int age;
   private int weight;

   public Cat(String name, int age, int weight) {
       this.name = name;
       this.age = age;
       this.weight = weight;
   }

   public Cat() {
   }

   public void sayMeow() {
       System.out.println("Meow!");
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       // input parameter check
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       // input parameter check
       this.age = age;
   }

   public int getWeight() {
       return weight;
   }

   public void setWeight(int weight) {
       // input parameter check
       this.weight = weight;
   }
}
Run Code

По сути, ограничение доступа к полям и использование геттеров/сеттеров — это и есть типичные случаи использования private в реальной работе.

Иными словами, главная цель модификатора private обеспечить инкапсуляцию.

Кстати, это относится не только к полям. Представьте, что в программе есть метод, который реализует ОЧЕНЬ сложную логику.

Что мы можем предложить в качестве примера?

Например, readDataFromCollider(): получает адрес данных, считывает байты из Большого адронного коллайдера, преобразует их в текст, записывает в файл и выводит результат на печать.

Очевидно, что код в этом случае будет громоздким.

Чтобы сделать его понятным и читабельным, логику лучше разбить на отдельные вспомогательные методы.

Например, метод readByteData() отвечает за чтение данных, метод convertBytesToSymbols() преобразует данные в текст, метод saveToFile() сохраняет полученный текст в файл, а метод printColliderData() выводит содержимое файла в консоль.

В итоге наш метод readDataFromCollider() станет намного проще:

public class ColliderUtil {

   public void readDataFromCollider(Path pathToData) {
       byte[] colliderData = readByteData(pathToData);
       String[] textData = convertBytesToSymbols(colliderData);
       File fileWithData = saveToFile(textData);
       printColliderData(fileWithData);
   }

   public byte[] readByteData(Path pathToData) {

       // Reads data in bytes
   }

   public String[] convertBytesToSymbols(byte[] colliderDataInBytes) {

       // Converts bytes to characters
   }

   public File saveToFile(String[] colliderData) {

       // Saves read data to a file
   }

   public void printColliderData(File fileWithColliderData) {

       // Prints data from the file
   }
}
Run Code

Но вспомним, как устроены интерфейсы: пользователь получает доступ только к внешнему интерфейсу. А наши четыре метода к нему не относятся.

Это вспомогательные методы: мы создали их, чтобы улучшить читаемость кода и не запихивать четыре разные задачи в один метод.

Нет необходимости предоставлять пользователю доступ к этим методам. Если пользователи будут иметь доступ к методу convertBytesToSymbols() при работе с коллайдером, они, скорее всего, просто запутаются в методе и зададутся вопросом, для чего он нужен. Что это за байты? Откуда они взялись? Зачем преобразовывать их в текст?

Логика, реализованная в этом методе, не входит в интерфейс, предназначенный для пользователя. Единственный метод, который должен быть публичным и доступным пользователю — это readDataFromCollider().

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

Благодаря этому они смогут спокойно выполнять свою работу внутри класса, не сбивая с толку пользователя, которому не обязательно знать, как работает каждый из этих методов.

public class ColliderUtil {

   public void readDataFromCollider(Path pathToData) {
       byte[] colliderData = readByteData(pathToData);
       String[] textData = convertBytesToSymbols(colliderData);
       File fileWithData = saveToFile(textData);
       printColliderData(fileWithData);
   }

   private byte[] readByteData(Path pathToData) {
       // Reads data in bytes
   }

   private String[] convertBytesToSymbols(byte[] colliderDataInBytes) {
       // Converts bytes to characters
   }

   private File saveToFile(String[] colliderData) {
       // Saves read data to a file
   }

   private void printColliderData(File fileWithColliderData) {
       // Prints data from the file
   }
}
Run Code

Модификатор protected

Следующий по строгости модификатор — protected.

Модификатор protected

Поля и методы, помеченные модификатором protected, будут видны:

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

Сначала может быть непонятно, когда это может понадобиться. И действительно: случаев, когда действительно нужен protected, гораздо меньше, чем для private, и все они довольно специфичны.

Представьте, что у нас есть абстрактный класс AbstractSecretAgent, описывающий секретного агента какой-либо спецслужбы, а также пакет top_secret, содержащий этот класс и его потомков. Конкретные классы, такие как FBISecretAgent, MI6SecretAgent, MossadSecretAgent и т. д., наследуют этот базовый класс.

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

package top_secret;

public abstract class AbstractSecretAgent {

   public static int agentCount = 0;
}
Run Code

Но наши агенты – секретные! А это значит, что только они должны знать, сколько их на самом деле существует.

Мы можем легко добавить модификатор protected к полю agent_counter. Тогда доступ к его значению смогут получить только экземпляры других секретных агентов и классы из того же пакета top_secret.

public abstract class AbstractSecretAgent {

   protected static int agent_counter = 0;
}
Run Code

Именно для таких специализированных задач и нужен модификатор protected 🙂

Модификатор доступа по умолчанию (package visible)

Модификатор доступа по умолчанию (также называемый package-private или package visible) — это следующий по строгости модификатор после protected. Он не указывается явно: если вы не указываете public, protected или private, то используется именно этот модификатор.

Если вы напишете в своем коде следующее:

int x = 10

то переменная x получит доступ по умолчанию — ее будет видно только в пределах пакета.

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

Пример: представим, что у нас есть пакет services. Он содержит различные классы, которые работают с базой данных. Например, UserService — читает данные о пользователях, CarService — данные об автомобилях. Каждый класс работает со своим типом объектов и извлекает соответствующую информацию:

package services;

public class UserService {
}

package services;

public class CarService {
}
Run Code

Но бывает, что данные в базе данных хранятся в одном формате, а нам нужен другой.

Представьте, что даты рождения пользователей в базе данных хранятся в формате <TIMESTAMP WITH TIME ZONE>…

2014-04-04 20:32:59.390583+02

… а вместо этого нам нужно просто получить объект java.util.Date.

Чтобы решить эту задачу, мы можем создать внутри пакета services специальный класс Mapper, который будет отвечать за преобразование данных из базы в Java-объекты. Простой вспомогательный класс.

Обычно мы объявляем все классы как public class ClassName, но это не обязательно.

Мы можем просто написать:

package services;

class Mapper {
}


package services;

public class CarService {

   Mapper mapper;
}
Run Code

Такой класс будет доступен только внутри пакета services:

package services;

public class CarService {

   Mapper mapper;
}
Run Code

И логика этого проста: зачем другим видеть вспомогательный класс, если он нужен только внутри пакета?

Модификатор public

И, наконец, последний в списке, но не по значимости — модификатор public!

После изучения интерфейсов становится понятно, что public предназначен для открытия метода или класса для внешнего использования.

Допустим, вы написали программу-переводчик, которая умеет переводить текст с русского на английский, и реализовали метод translate(String textInRussian), в котором описана вся логика перевода.

Метод отмечен словом public — значит, он теперь часть интерфейса класса:

public class Translator {

   public String translate(String textInRussian) {

       // Translates text from Russian to English
   }
}
Run Code

Теперь можно привязать этот метод к кнопке “Перевести” на экране, и все — любой может им воспользоваться.

Все части программы, отмеченные public, предназначены для конечного пользователя.

Если привести аналогию с реальной жизнью: private — это всё, что происходит внутри телевизора, а public — это кнопки на пульте управления.
Пользователю совершенно не нужно знать, как устроен телевизор изнутри — ему важно, чтобы кнопки работали: on(), off(), nextChannel(), increaseVolume(), и так далее.

Чтобы закрепить полученные знания, мы рекомендуем посмотреть видеоурок (на английском языке):

Теперь, когда вы узнали о модификаторах доступа Java — private, default, protected и public — объединим все вышеописанное в простую и удобную для чтения сравнительную таблицу.

Эта таблица поможет увидеть различия между модификаторами и понять, какой из них использовать в той или иной ситуации.

Сравнительная таблица модификаторов доступа

МодификаторКлассПакетПодкласс (в том же пакете)Подкласс (в другом пакете)Другие классы
private✔️
default (без модификатора)✔️✔️✔️
protected✔️✔️✔️✔️
public✔️✔️✔️✔️✔️

Что это значит?

  • ✔️ – Доступно
  • – Недоступно

Краткие сведения

  • private: доступен только в пределах текущего класса. Рекомендуется для инкапсуляции.
  • default: доступ ограничен текущим пакетом. Модификатор не требуется.
  • protected: доступен в текущем пакете и в производных классах из других пакетов. Подходит для наследования.
  • public: доступен повсеместно. Используется для публичных интерфейсов и открытых методов.

Рекомендации по использованию

Вот небольшая шпаргалка, которая поможет вам определиться с выбором:

  • private — если нужно скрыть логику и защитить внутренние данные.
  • default — при использовании компонентов в рамках одного пакета.
  • protected — если нужно дать доступ подклассам (даже из других пакетов), но оставить скрытым для остальных.
  • public — если метод или класс должен быть доступен везде.

Перевод статьи «Access Modifiers in Java».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх