Professor Hans Noodles
41 уровень

Модификаторы доступа. Private, protected, default, public

Статья из группы Java Developer
Привет! В сегодняшней лекции мы познакомимся с понятием «модификаторы доступа» и рассмотрим примеры работы с ними. Модификаторы доступа. Private, protected, default, public - 1Хотя слово «познакомимся» будет не совсем правильным: с большинством из них ты уже знаком по предыдущим лекциям. На всякий случай освежим в памяти главное. Модификаторы доступа — это чаще всего ключевые слова, которые регулируют уровень доступа к разным частям твоего кода. Почему «чаще всего»? Потому что один из них установлен по умолчанию и не обозначается ключевым словом :) Всего в Java есть четыре модификатора доступа. Перечислим их в порядке от самых строгих до самых «мягких»:
  • private;
  • protected;
  • default (package visible);
  • public.
Давай рассмотрим каждый из них, определимся, когда они могут нам пригодиться и приведем примеры :)

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

Модификаторы доступа. Private, protected, default, public - 2Private — наиболее строгий модификатор доступа. Он ограничивает видимость данных и методов пределами одного класса. Этот модификатор тебе известен из лекции про геттеры и сеттеры. Помнишь этот пример?

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("Мяу!");
   }
}

public class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();
       cat.name = "";
       cat.age = -1000;
       cat.weight = 0;
   }
}
Мы рассматривали его в одной из статей раньше. Здесь мы допустили серьезную ошибку: открыли наши данные, в результате чего коллеги-программисты получили доступ напрямую к полям класса и изменению их значения. Более того, эти значения присваивались без проверок, в результате чего в нашей программе можно создать кота с возрастом -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("Мяу!");
   }

   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;
   }
}
Собственно, ограничение доступа к полям и реализация геттеров-сеттеров — самый распространенный пример использования 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) {

       // считывает данные в байтах
   }

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

       // конвертирует байты в символы
   }

   public File saveToFile(String[] colliderData) {

       // сохраняет считанные данные в файл
   }

   public void printColliderData(File fileWithColliderData) {

       // печатает данные из файла
   }
}
Однако, как ты помнишь из лекции про интерфейсы, пользователь получает доступ только к конечному интерфейсу. А наши 4 метода не являются его частью. Они вспомогательные: мы создали их, чтобы улучшить читаемость кода и не засовывать четыре разные задачи в один метод. Давать доступ пользователю к этим методам не нужно. Если у пользователя при работе с коллайдером появится доступ к методу 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) {
       // считывает данные в байтах
   }

   private String[] convertBytesToSymbols(byte[] colliderDataInBytes) {
       // конвертирует байты в символы
   }

   private File saveToFile(String[] colliderData) {
       // сохраняет считанные данные в файл
   }

   private void printColliderData(File fileWithColliderData) {
       // печатает данные из файла
   }
}

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

Следующий по строгости модификатор доступа — protected. Модификаторы доступа. Private, protected, default, public - 3 Поля и методы, обозначенные модификатором доступа protected, будут видны:
  • в пределах всех классов, находящихся в том же пакете, что и наш;
  • в пределах всех классов-наследников нашего класса.
Сходу трудно представить, когда это может понадобиться. Не удивляйся: случаев применения protected гораздо меньше, чем private, и они специфические. Представь, что у нас есть абстрактный класс AbstractSecretAgent, обозначающий секретного агента какой-то спецслужбы, а также пакет top_secret, в котором лежит этот класс и его наследники. От него наследуются конкретные классы — FBISecretAgent, MI6SecretAgent, MossadSecretAgent и т.п. Внутри абстрактного класса мы хотим реализовать счетчик агентов. При создании где-то в программе нового объекта-агента он будет увеличиваться.

package top_secret;

public abstract class AbstractSecretAgent {

   public static int agentCount = 0;
}
Но агенты-то у нас секретные! А значит, об их числе должны знать только они и никто другой. Мы легко можем добавить модификатор protected к полю agentCount, и тогда получить его значение смогут либо объекты других классов секретных агентов, либо те классы, которые расположены в нашем «секретном» пакете top_secret.

public abstract class AbstractSecretAgent {

   protected static int agentCount = 0;
}
Вот для таких специфических задач и нужен модификатор protected :)

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

Дальше у нас по списку идет модификатор default или, как его еще называют, package visible. Он не обозначается ключевым словом, поскольку установлен в Java по умолчанию для всех полей и методов. Если написать в твоем коде —

int x = 10
… у переменной x будет этот самый package visible доступ. Запомнить, что он делает, легко. По сути, default = protected-наследование :) Случаи его применения ограничены, как и у модификатора protected. Чаще всего default-доступ используется в пакете, где есть какие-то классы-утилиты, не реализующие функциональность всех остальных классов в этом пакете. Приведем пример. Представь, что у нас есть пакет «services». Внутри него лежат различные классы, которые работают с базой данных. Например, есть класс UserService, считывающий данные пользователей из БД, класс CarService, считывающий из этой же БД данные об автомобилях, и другие классы, каждый из которых работает со своим типом объектов и читает данные о них из базы.

package services;

public class UserService {
}

package services;

public class CarService {
}
Однако легко может случиться ситуация, когда данные в базе данных лежат в одном формате, а нам они нужны в другом. Представь, что дата рождения пользователя в БД хранится в формате TIMESTAMP WITH TIME ZONE...

2014-04-04 20:32:59.390583+02
...нам вместо этого нужен самый простой объект — java.util.Date. Для этой цели можем создать внутри пакета services специальный класс Mapper. Он будет отвечать за конвертацию данных из базы в привычные нам Java-объекты. Простой вспомогательный класс. Обычно мы создаем все классы как public class ClassName, но это не обязательно. Мы можем объявить наш вспомогательный класс просто как class Mapper. В таком случае он все равно делает свою работу, но не виден никому за пределами пакета services!

package services;

class Mapper {
}


package services;

public class CarService {
  
   Mapper mapper;
}
А это, по сути, правильная логика: зачем кому-то за пределами пакета видеть вспомогательный класс, работающий только с классами этого же пакета?

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

И последний по списку, но не по значимости — модификатор public! С ним ты познакомился в первый день учебы на JavaRush, впервые в жизни запустив public static void main(String[] args). Модификаторы доступа. Private, protected, default, public - 4 Теперь, когда ты изучил лекции об интерфейсах, для тебя очевидно его предназначение :) Ведь public создан для того, чтобы отдавать что-то пользователям. Например, интерфейс твоей программы. Допустим, ты написал программу-переводчик, и она умеет переводить русский текст в английский. Ты создал метод translate(String textInRussian), внутри которого реализована необходимая логика. Этот метод ты отметил словом public, и теперь он станет частью интерфейса:

public class Translator {

   public String translate(String textInRussian) {

       // переводит текст с русского на английский
   }
}
Можно связать вызов этого метода с кнопкой «перевести» на экране программы — и все! Кто угодно может этим пользоваться. Части кода, помеченные модификатором public, предназначаются для конечного пользователя. Если привести пример из жизни, private — это все процессы, происходящие внутри телевизора, когда он работает, а public — это кнопки на пульте телевизора, с помощью которых пользователь может им управлять. При этом ему не нужно знать как устроен телевизор и за счет чего он работает. Пульт — это набор public-методов: on(), off(), nextChannel(), previousChannel(), increaseVolume(), decreaseVolume() и т.д.
Комментарии (114)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
worried-cell Уровень 24, Киев
6 сентября 2022
повторение, мать учения!)
Aliaksei Iyunski Уровень 14, Минск, Belarus
9 августа 2022
Не вводите людей в заблуждение!!! У вас модификаторы доступа указаны не в правильном порядке!!! Вот список модификаторов от самого строгого: 1. Private 2. Default (package visible) 3. Protected 4. Public Вот ссылка на документацию Oracle: Модификаторы доступа в Java
Suzuya Jūzō Уровень 26, Russian Federation
13 июля 2022
Уважаемые разработчики исправьте уже этот косяк... Всего в Java есть четыре модификатора доступа. Перечислим их в порядке от самых строгих до самых «мягких»: private; default (package visible); protected; public.
Дмитрий Воробьёв Уровень 14, Санкт-Петербург, Russian Federation
14 июня 2022
я из 2022, где лекция про геттеры и сеттеры?
Белич Максим Уровень 16, Минск, Беларусь
30 мая 2022
Скриншот с официального сайта oracle. Кто скажет что тут не так? Кто увидит тот молодец. Здесь ошибка. Доступа к protected переменной нету. С protected не так всё просто.
Ant Уровень 14, Красноярск, Россия
21 мая 2022
Картинка для наглядно представления
AlekseyS Уровень 48, Москва, Russian Federation
26 апреля 2022
Хорошая статья, но не указали, как работают данные модификаторы, если прописаны не у методов/полей/переменных, а непосредственно у классов. Обычный (не inner/nested) класс не может быть private (это бы изолировало его от остальной программы) или protected (не имеет смысла), соответственно он может быть либо public, либо default (default - ограничивает доступ ко всему из этого класса для другого класса другого пакета). При этом в каждом файле класса должен быть один public класс с именем файла. Inner/nested класс уже может быть и private и protected.
Валентин Еременко Уровень 26, Волгодонск, Россия
14 декабря 2021
1. Не совсем понял, почему protected строже default. У дефолтного ведь доступ внутри пакета только, а у протектед еще и наследники получат доступ, даже если они в другом пакете. Я что-то путаю? 2. у нас default - это package visible или package private? А то в одном месте говорят "обычно его зовут так", в другом "обычно его зовут иначе". Как это понимать?
Павел Уровень 35
9 сентября 2021
а этот код что напечатает в main класса Y:

package p1;
public class X {
    protected void abc(){
        System.out.println("JR is cool or ...");
    }
}
package p2;

import p1.X;
public class Y extends X {
    public static void main(String[] args) {
        X x = new X();
        x.abc();
    }
}
Тут нужно очень четко понимать, что к чему!
Mikh Bear Уровень 42, Москва, Россия
17 августа 2021
то есть если метод protected, то он будет виден только своим и наследникам, даже если в чужом пакете?