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

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

В этой статье мы познакомимся с понятием модификаторов доступа и рассмотрим примеры их использования.

Что такое модификаторы доступа?

Модификаторы доступа в Java – это ключевые слова, которые определяют, кто может использовать или изменять данные и методы в вашем коде. Это как уровни доступа к вещам в реальной жизни: что-то можно показать всем, что-то – только своим близким, а что-то оставить в секрете.

В Java есть четыре модификатора доступа, и вот они в порядке от самого закрытого до самого открытого:

  • private – доступно только внутри одного класса. Если что-то помечено как private, это видно и используется только внутри текущего класса.
  • default (package visible) – доступно по умолчанию. Если не указывать модификатор, доступ будет открыт только для классов в том же пакете.
  • protected – доступно для подклассов и внутри одного пакета.
  • public – доступно для всех.

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

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

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

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

В коде выше мы допустили серьезную ошибку: сделали наши данные публичными, из-за чего другие программисты могли напрямую изменять их значения. Более того… значения устанавливались без проверок! В итоге программа могла создать кота с именем – "", возрастом – 1000 лет и весом – 0.

Решить эту проблему можно, если:

  1. Использовать геттеры и сеттеры.
  2. Применить модификатор 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) {
       
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       
       this.age = age;
   }

   public int getWeight() {
       return weight;
   }

   public void setWeight(int weight) {
       
       this.weight = weight;
   }
}
Run Code

Закрытие полей и использование геттеров/сеттеров – один из самых частых примеров применения private в реальной разработке. Главная цель private – инкапсуляция: защита данных от некорректных изменений извне.

Но private применяется не только к полям!

Представьте, что в программе есть очень сложный метод. Допустим, метод readDataFromCollider() выполняет следующие задачи:

  • принимает адрес данных;
  • считывает данные с Большого адронного коллайдера в байтовом формате;
  • конвертирует байты в текст;
  • сохраняет результат в файл;
  • выводит данные на экран.

Даже описание метода выглядит страшно, не говоря уже о коде!

Вместо того, чтобы писать всю логику в одном месте, мы разобьем ее на отдельные методы:

  • readByteData() – считывает данные;
  • convertBytesToSymbols() – преобразует байты в текст;
  • saveToFile() – сохраняет текст в файл;
  • printColliderData() – выводит данные на экран.
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) {

       
   }

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

       
   }

   public File saveToFile(String[] colliderData) {

       
   }

   public void printColliderData(File fileWithColliderData) {

       
   }
}
Run Code

Теперь метод readDataFromCollider() станет намного проще.

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

Правильное решение?

Скрываем внутреннюю логику с помощью модификатора 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) {
       
   }

   private String[] convertBytesToSymbols(byte[] colliderDataInBytes) {
       
   }

   private File saveToFile(String[] colliderData) {
       
   }

   private void printColliderData(File fileWithColliderData) {
       
   }
}
Run Code

Использование private позволяет:

  1. Защитить данные от прямого изменения.
  2. Скрыть сложную внутреннюю логику и не перегружать пользователя ненужной информацией.
  3. Сделать код чище, понятнее и удобнее для работы.

Инкапсуляция = безопасность + структурированность!

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

Следующим по значимости ограничивающим параметром является protected.

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

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

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

Вариантов использования protected гораздо меньше, чем private, и они очень специфичны.

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

Пакет: 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

Модификатор package visible

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

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

int x = 10
Run Code

Переменная x будет иметь доступ к этому пакету. Запомнить его работу легко. По сути, default = protected, но без наследования.

Как у модификатора protected, его применение ограничено. Чаще всего доступ по умолчанию (default) используется в пакете, который содержит вспомогательные классы, не реализующие функциональность всех остальных классов в этом пакете.

Теперь давайте разберем пример.

Представим, что у нас есть пакет services, который содержит различные классы для работы с базой данных. Например:

  • UserService — считывает данные о пользователях из базы;
  • CarService — считывает данные об автомобилях из той же базы;
  • и другие классы, каждый из которых работает с определенными объектами и получает соответствующие данные.
package services;

public class UserService {
}

package services;

public class CarService {
}
Run Code

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

Допустим, в базе данных даты рождения пользователей хранятся в виде <TIMESTAMP WITH TIME ZONE>, а нам нужен обычный объект java.util.Date.

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

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

А теперь самое интересное!

Обычно мы пишем все классы с модификатором public, типа public class ClassName, но это не обязательное правило. Мы можем объявить наш класс Mapper без public, просто как class Mapper. В этом случае он по-прежнему выполняет свою работу, но не виден никому за пределами пакета services!

Логика простая: зачем кому-то извне видеть вспомогательный класс, который работает только с классами внутри своего пакета? Поэтому вместо того, чтобы объявлять его как public class Mapper, мы просто пишем:

package services;

class Mapper {
}


package services;

public class CarService {

   Mapper mapper;
}
Run Code

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

И последнее, но не менее важное: модификатор public!

Предположим, вы написали программу-переводчик, которая может переводить текст с русского на английский. Вы создали метод translate(String textInRussian), который реализует всю необходимую логику. Вы пометили этот метод словом public, и теперь он является частью интерфейса: Вы можете привязать этот метод к кнопке Перевести на экране – и готово! Любой сможет его использовать.

public class Translator {

   public String translate(String textInRussian) {

       
   }
}
Run Code

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

Все, что помечено модификатором public, – это то, что ты даёшь конечному пользователю. Это открытые функции, с которыми будет работать человек.

Вот вам пример:

  • private – это все процессы, происходящие внутри телевизора;
  • public — это кнопки на пульте, с помощью которых пользователь управляет телевизором.

При этом пользователю не нужно знать, как именно устроен телевизор или как он работает.

Пульт — это набор публичных методов:

  • on() – включить;
  • off() – выключить;
  • nextChannel() – следующий канал;
  • previousChannel() – предыдущий канал;
  • increaseVolume() – увеличить громкость;
  • decreaseVolume() – уменьшить громкость.

Главное – дать пользователю удобный способ взаимодействия, скрывая внутреннюю реализацию.

Чтобы еще лучше закрепить материал, посмотрите этот видеоурок:

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

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

МодификаторВнутри классаВнутри пакетаПодкласс (в том же пакете)Подкласс (в другом пакете)Другие классы
private✔️
default✔️✔️✔️
protected✔️✔️✔️✔️
public✔️✔️✔️✔️✔️
  • ✔️ – Доступно
  • – Недоступно

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

  • private – доступно только внутри класса. Отлично подходит для инкапсуляции и защиты данных;
  • default (без модификатора) – доступно в пределах одного пакета. Удобно, если работа идет только внутри пакета;
  • protected – доступно внутри пакета и в подклассах из других пакетов. Идеально для наследования;
  • public – доступно везде. Используется, когда код должен быть полностью открыт.

Когда использовать какой модификатор?

Вот короткая шпаргалка:

  • **private** – когда нужно скрыть внутреннюю логику и защитить важные данные.
  • **default** – если работа идет внутри одного пакета, и доступ извне не нужен
  • **protected** – когда нужно разрешить доступ в подклассах, даже в других пакетах, но скрыть от остальных.
  • **public** – если метод или класс должен быть доступен везде.

Выбирай модификатор осознанно! Чем меньше область видимости – тем безопаснее код.

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

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

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

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