Привіт! На сьогоднішній лекції ми познайомимось з поняттям «модифікатори доступу» та розглянемо приклади роботи з ними.
Хоча слово «познайомимось» буде не зовсім правильним: з більшістю з них ти вже знайомий з попередніх лекцій. Про всяк випадок освіжимо в пам’яті головне.
Модифікатори доступу — це найчастіше ключові слова, які регулюють рівень доступу до різних частин твого коду. Чому «найчастіше»? Тому що один з них встановлений за замовчуванням і не позначається ключовим словом :)
Всього в Java є чотири модифікатори доступу. Перелічимо їх у порядку від найсуворіших до найбільш «м’яких»:
![Модифікатори доступу. Private, protected, default, public - 2]()
Поля та методи, позначені модифікатором доступу
Тепер, коли ти вивчив лекції про інтерфейси, для тебе очевидно його призначення :) Адже
Хоча слово «познайомимось» буде не зовсім правильним: з більшістю з них ти вже знайомий з попередніх лекцій. Про всяк випадок освіжимо в пам’яті головне.
Модифікатори доступу — це найчастіше ключові слова, які регулюють рівень доступу до різних частин твого коду. Чому «найчастіше»? Тому що один з них встановлений за замовчуванням і не позначається ключовим словом :)
Всього в Java є чотири модифікатори доступу. Перелічимо їх у порядку від найсуворіших до найбільш «м’яких»:
- private;
- default (package visible);
- protected;
- public.
Модифікатор private

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("Мяу!");
}
}
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) {
// друкує дані з файлу
}
}
Модифікатор 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;
}
А це, по суті, правильна логіка: навіщо комусь за межами пакета бачити допоміжний клас, що працює лише з класами цього ж пакета?Модифікатор protected
Наступний за строгостю модифікатор доступу —protected.
Поля та методи, позначені модифікатором доступу 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 :)Модифікатор public
І останній за списком, але не за значенням — модифікаторpublic! З ним ти познайомився в перший день навчання на JavaRush, вперше в житті запустивши public static void main(String[] args).
Тепер, коли ти вивчив лекції про інтерфейси, для тебе очевидно його призначення :) Адже public створений для того, щоб передавати щось користувачам. Наприклад, інтерфейс твоєї програми.
Припустимо, ти написав програму-перекладач, і вона вміє перекладати російський текст англійською. Ти створив метод translate(String textInRussian), всередині якого реалізована необхідна логіка.
Цей метод ти позначив словом public, і тепер він стане частиною інтерфейсу:
public class Translator {
public String translate(String textInRussian) {
// перекладає текст із російської на англійську
}
}
Можна пов'язати виклик цього методу з кнопкою «перекласти» на екрані програми — і все! Хто завгодно може цим користуватися.
Частини коду, позначені модифікатором public, призначені для кінцевого користувача.
Якщо навести приклад із життя, private — це всі процеси, що відбуваються всередині телевізора, коли він працює, а public — це кнопки на пульті телевізора, за допомогою яких користувач може ним керувати. При цьому йому не потрібно знати, як влаштований телевізор і за рахунок чого він працює. Пульт — це набір public-методів: on(), off(), nextChannel(), previousChannel(), increaseVolume(), decreaseVolume() і т.д.