JavaRush/Java блог/Java Developer/Модификаторы доступа. Private, protected, default, public...
Автор
Артем Divertitto
Senior Android-разработчик в United Tech

Модификаторы доступа. 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 доступ. Если метод (или переменная) не помечены никаким модификатором, считается, что они помечены «модификатором по умолчанию». Переменные или методы с таким модификатором (т.е. вообще без какого-нибудь) видны всем классам пакета, в котором они объявлены. И только им. Случаи его применения ограничены, как и у модификатора 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() и т.д.
Комментарии (137)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Viacheslav B. System Administrator
3 февраля, 20:19
стаття не для 7 рівня 7 лекції, можно додати у закладки , щоб повернутися пізніше
Lanson QA Automation Engineer
25 января, 12:37
Очень доступное объяснение, спасибо!
rman
Уровень 21
17 декабря 2023, 18:31
Примеры понравились, спасибо! Уяснил protected и default
Anonymous #3291313
Уровень 51
3 сентября 2023, 11:14
Концовка только была внятно понятно рассказана, не пойму почему примеры такие же доходчивые с protected и default не привели.
Anonymous #3291313
Уровень 51
3 сентября 2023, 10:08
Я что-то не пойму, здесь всех устраивает что ничего до этого не было в обучающем материале об инкапсуляции, геттерах и сеттерах, а в этой статье об этом так говорят типа все тут уже в курсе на счёт этого?
Andrei Karavai
Уровень 29
10 декабря 2023, 18:11
Вероятно в разных языковых версиях разный контент. В английской версии про геттеры и сеттеры ничего не было, но, вероятно, авторы уверены, что геттеры и сеттеры можно самостоятельно осилить, раз уж английский выучил😁
mz
Уровень 10
17 декабря 2023, 17:49
Согласен!
Andrei #3394750
Уровень 10
Expert
5 февраля, 13:58
Это не про уроки. Это про статьи в блоге. Вот статься про Геттеры и сеттеры https://javarush.com/groups/posts/1928-getterih-i-setterih
kiranis QA Automation Engineer
14 июня 2023, 10:59
Мне кажется или в статье ошибка? Порядок модификаторов по строгости должен быть: - private - default - protected - public ?
Tatyana Kiselyova
Уровень 9
19 июня 2023, 18:19
protected виден не только в пакете, но и классам-наследникам. Вот хорошая табличка из лекции https://javarush.com/quests/lectures/questsyntaxpro.level06.lecture03
Денис Стёпшин
Уровень 25
20 июня 2023, 04:46
тоже считаю, что перепутано. и табличка в комментарии выше - тому подтверждение.
Lexoid
Уровень 40
29 июня 2023, 14:30
Однозначно перепутано. Protected даёт возможность получить доступ к тому или иному члену класса даже в том случае, если класс-потомок, который пытается получить доступ к члену, находится за пределами пакета рассматриваемого (целевого) класса. Таким образом, package-private (он же представляет собой уровень доступа по умолчанию) уступает по "строгости" лишь модификатору private.
Alexander Smith Java Developer
17 января, 09:21
Конечно ошибка! Мало того, если при переопределении базового protected-метода в производном классе не указать модификатор доступа (установить модификатор по умолчанию), возникнет ошибка компилятора, т. к. модификатор не должен быть более закрытым.
Anatoly Enterprise Java Developer
19 мая 2023, 18:04
нравится)
Sergey
Уровень 1
3 мая 2023, 06:36
Очень доходчиво
Ислам
Уровень 33
21 апреля 2023, 09:37
Классная лекция с отличными примерами.
13 февраля 2023, 17:34
Супердоходчиво