Вітання! У сьогоднішній лекції ми познайомимося з поняттям « модифікатори доступу » та розглянемо приклади роботи з ними. Хоча слово «познайомимося» буде не зовсім правильним: з більшістю з них ти вже знайомий із попередніх лекцій. Про всяк випадок освіжимо в пам'яті головне. Модифікатори доступу – це найчастіше ключові слова, які регулюють рівень доступу до різних частин твого коду. Чому «найчастіше»? Тому що один з них встановлений за замовчуванням і не позначається ключовим словом :) Всього Java має чотири модифікатори доступу. Перерахуємо їх у порядку від найсуворіших до «м'яких»:
- private;
- protected;
- default (package visible);
- 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) {
// Друкує дані з файлу
}
}
Модифікатор 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
:)
Модифікатор 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
! З ним ти познайомився в перший день навчання на 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()
і т.д.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ