Назад Зміст Вперед

6.4. Модифікатори доступу

Рівні доступу при зазначенні відповідних специфікаторів в описі.
Таблиця. Доступ до атрибутів і методів класу
Специфікатор  
Доступ з того ж класу  
Доступ з того ж пакету  
Доступ з підкласу
Доступ з довільного місця програми
public
+
+
+
+
protected
+
+
+
-
Без специфікатора
+
+
-
-
private
+
-
-
-

public

Доступний всім!
Приклад public елементів:
class A {
  public int m;
  public void f() {
  }
  public static String name = "Test";
}

Тепер подивимося як цим користуються:
class B {
  void someFunction(){
    String myString = A.name; /* До публічних статичних членів класу можна звернутися навіть не маючи примірника*/
    A a = new A();            /* створюємо екземпляр */
    a.m = 10;                 /* Доступ до публічного цілого члену класу такий як до звичайної цілої змінної */
    a.m++; 
    a.f();          /* Функція також доступна з будь якого місця */
  }
}

protected

Доступний всередині пакету й своїм підкласам з інших пакетів.
class A {
  protected int x;
  protected void f() {}
  protected static String name = "Ku-Ku";
}

class B extends A {
  public void someFunc() {
    x = 10; //доступ до захищеного елементу батьківського класу
    f(); 
    A ref = new A();
    ref.x = 13; 
    String bebe = A.name; //статичні змінні - не виключення
  }
}

class C {
  public void someFunc() {
    String mmm = A.name; //неправильний виклик. Змінна name - захищена і її не видно ззовні (тільки, якщо клас С знаходиться 
//в іншому пакеті. Якщо клас С знаходиться в тім же пакеті, що і А, то буде доступна)
    A a = new A();
    int myX = a.x; //Неправильний виклик. Змінну x - не видно ззовні(Тільки, якщо клас С знаходиться в іншому пакеті. Якщо 
                   //клас С знаходиться в тім же пакеті, що і А, то буде доступна)
    a.x = 1; //Неправильний виклик. }}
    a.f(); //Неправильний виклик. Функцію f - не видно ззовні (Тільки, якщо клас С знаходиться в іншому пакеті. Якщо клас С знаходиться в тім же пакеті, що і А, то буде доступна)}}
  }
}

package protected

Доступний в межах свого пакета (package). Присвоюється автоматично, якщо не вказано, тобто, Int i - це все одно що protected int i.
class A { //Цей клас видно тільки в пакеті, в якому він знаходиться сам
  protected int x;  //Члени класу видно для всіх класів даного пакету
}

private

Доступний тільки усередині свого класу.
class A {
  private int x; //змінну x видно виключно всередині класу A
  public void setX(int val) { //зазвичай таку функцію називають "setter"
    x = val;
  }
  public int getX() { //зазвичай таку функцію називають "getter"
    return x;
  }
}
class B extends A {
  public void someFunc() {
    this.x = 10; //Помилка! Приватні змінні не видно навіть наслідникам
  }
}

getX і setX - є функціями доступу до приватної змінної. Якщо ми вирішимо, що змінна x може містити виключно числа від 1 до 100, то нам не знадобиться шукати всі звернення до x по всіх класах, а просто додати перевірку між рядками 4 та 5.

10 заміток про модифікатор Static в Java

Модифікатор static в Java безпосередньо пов'язаний з класом, 
якщо поле статичне, значить воно належить класу
якщо метод статичний, аналогічно - він належить класу

Виходячи з цього, можна звертатися до статичного методу або поля, використовуючи ім'я класу. 

Наприклад, якщо поле count статичне в класі Counter, значить, ви можете звернутися до змінної запитом виду: Counter.count

Звичайно, слід враховувати модифікатори доступу. Поля private доступні тільки усередині класу, в якому вони оголошені. Поля protected доступні всім класам всередині пакету (package), а також всіх класах-спадкоємцям поза пакетом. 

Припустимо, існує статичний метод increment () в класі Counter, завданням якого є інкрементувати лічильника count. Для виклику даного методу можна використовувати звертання виду Counter.increment (). Немає необхідності створювати екземпляр класу Counter для доступу до статичного поля або методу. Це фундаментальна відмінність між статичними і НЕ статичними об'єктами (членами класу).

Важливе зауваження! Не забувайте, що статичні члени класу безпосередньо належать класу, а не його екземпляру. Тобто, значення статичної змінної count буде однакове для всіх об'єктів типу Counter

Що повинен знати кожен програміст про модифікаторі Static в Java


1) Ви НЕ можете отримати доступ до НЕ статичних членів класу, всередині статичного контексту, як варіант, методу або блоку. Результатом компіляції наведеного нижче коду буде помилка:

public class Counter{
private int count;
public static void main(String args[]){
   System.out.println(count); //compile time error
}}


Це одна з найбільш поширених помилок. Так як метод main статичний, а змінна count ні, то в цьому випадку метод println, всередині методу main викине "Compile time error".

2) На відміну від локальних змінних, статичні поля і методи НЕ потокобезпечні (Thread-safe) в Java. На практиці це одна з найбільш частих причин виникнення проблем пов'язаних з безпекою мультипоточного програмування. Тому при використанні статичних змінних, переконайтеся, що вони належним чином синхронізовані (synchronized).

3) Статичні методи мають перевагу в застосуванні, тому відсутня необхідність щоразу створювати новий об'єкт для доступу до таких методів. Статичний метод можна викликати, використовуючи тип класу, в якому ці методи описані. Саме тому, подібні методи якнайкраще підходять в якості методів-фабрик (factory), і методів-утиліт (utility). Клас java.lang.Math - чудовий приклад, в якому майже всі методи статичні, з цієї ж причини класи-утиліти в Java final.

4) Іншим важливим моментом є те, що ви НЕ можете перевизначити (Override) статичні методи. Якщо ви оголосите такий же метод в класі-спадкоємця (subclass), тобто метод з таким же ім'ям і сигнатурою, ви лише «заховаєте» метод суперкласу (superclass) замість перевизначення. Це явище відоме як приховування методів (hiding methods). Це означає, що при зверненні до статичного методу, який оголошений як в батьківському, так і в дочірньому класі, під час компіляції завжди буде викликаний метод виходячи з типу змінної. На відміну від перевизначення, такі методи не будуть виконані під час роботи програми. Розглянемо приклад:

class Vehicle{
     public static void  kmToMiles(int km){
          System.out.println("Всередині батьківського класу/статичного методу");
     } }
class Car extends Vehicle{
     public static void  kmToMiles(int km){
          System.out.println("Всередині дочірнього класу/статичного методу");
     } }
public class Demo{   
   public static void main(String args[]){
      Vehicle v = new Car();
       v.kmToMiles(10);
  }}


Вивід в консоль:Всередині батьківського класу/статичного методу

Код наочно демонструє: незважаючи на те, що об'єкт має тип Car, викликаний статичний метод з класу Vehicle, тому відбулося звернення до методу під час компіляції. І зауважте, помилки під час компіляції не виникло!

5) Оголосити статичним також можна і клас, за винятком класів верхнього рівня. Такі класи відомі як «вкладені статичні класи» (nested static class). Вони бувають корисними для представлення поліпшених зв'язків. Яскравий приклад вкладеного статичного класу - HashMap.Entry, який надає структуру даних усередині HashMap. Варто зауважити, також як і будь-який інший внутрішній клас, вкладені класи знаходяться в окремому файлі з розширенням .class. Таким чином, якщо ви оголосили п'ять вкладених класів у вашому головному класі, у вас буде 6 файлів з розширенням .class. Ще одним прикладом використання є оголошення власного компаратора (Comparator), наприклад компаратор за віком (AgeComparator) в класі співробітники (Employee).

6) Модифікатор static також може бути оголошений у статичному блоці, більш відомим як «Статичний блок ініціалізації» (Static initializer block), який буде виконаний під час завантаження класу. Якщо ви не оголосите такий блок, то Java збере всі статичні поля в один список і виконає його під час завантаження класу. Однак, статичний блок НЕ може прокинути перехоплені винятки, але може викинути НЕ перехоплені. У такому випадку виникне «Exception Initializer Error».

7) Корисно знати, що статичні методи зв'язуються під час компіляції, на відміну від зв'язування віртуальних або не статичних методів, які зв'язуються під час виконання на реальному об'єкті. Отже, статичні методи не можуть бути перевизначені в Java, тому поліморфізм під час виконання не поширюється на них. Це важливе обмеження, яке необхідно враховувати, оголошуючи метод статичним. У цьому є сенс, тільки тоді, коли немає можливості або необхідності перевизначення такого методу класами-спадкоємцями. Методи-фабрики і методи-утиліти хороші зразки застосування модифікатора static. Джошуа Блох виділив кілька переваг використання статичного методу-фабрики перед конструктором, в книзі «Effective Java», яка є обов'язковою для прочитання кожною програмістом даної мови.

8) Важливою властивістю статичного блоку є ініціалізація. Статичні поля або змінні ініціалізуються після завантаження класу в пам'ять. Порядок ініціалізації зверху вниз, в тому ж порядку, в якому вони описані у вихідному файлі Java класу. 

9) Під час серіалізації, також як і transient змінні, статичні поля НЕ серіалізуються. Дійсно, якщо зберегти будь-які дані в статичному полі, то після десеріалізації новий об'єкт буде містити його первинне (по-замовчуванню) значення, наприклад, якщо статичним полем була змінна типу int, то її значення після десеріалізації дорівнюватиме нулю, якщо типу float - 0.0 , якщо типу Object - null. Чесно кажучи, це один з найбільш поширених питань щодо серіалізації на співбесідах по Java. Не зберігайте найбільш важливі дані про об'єкт в статичному полі!

10) І наостанок, поговоримо про static import. Даний модифікатор має багато спільного зі стандартним оператором import, але на відміну від нього дозволяє імпортувати один або всі статичні члени класу. При імпортуванні статичних методів, до них можна звертатися як ніби вони визначені в цьому ж класі, аналогічно при імпортуванні полів, ми можемо отримати доступ без вказівки імені класу. Дана можливість з'явилася в Java версії 1.5, і при належному використанні покращує читабельність коду. Найбільш часто дана конструкція зустрічається в тестах JUnit, тому майже всі розробники тестів використовують static import для assert методів, наприклад assertEquals () і для їх перевантажених дублікатів. Якщо нічого не зрозуміло - ласкаво просимо за додатковою інформацією.

.