JavaRush /Java блог /Архив info.javarush /10 нотаток про модифікатор Static у Java
Автор
Pavlo Plynko
Java-разработчик в CodeGym

10 нотаток про модифікатор Static у Java

Стаття з групи Архив info.javarush
Модифікатор static у Java безпосередньо пов'язаний із класом. Якщо поле статичне, значить, воно належить класу, якщо метод статичний – аналогічно: він належить класу. Виходячи з цього, можна звертатися до статичного методу або поля, використовуючи ім'я класу. Наприклад, якщо поле count статичне в класі Counter, отже, ви можете звернутися до змінної із запитом в такій формі: Counter.count. 10 нотаток про модифікатор Static у Java - 1Перед тим, як звернутися до нотаток, згадаймо (а можливо, дізнаємося), що таке static і що може бути статичним у Java. Static – модифікатор, що застосовується до поля, блоку, методу або внутрішнього класу. Цей модифікатор вказує на прив'язку суб'єкта до поточного класу.

Статичні поля

У разі позначення змінної рівня класу ми вказуємо на те, що це значення належить до класу. Якщо цього не робити, то значення змінної буде прив'язуватися до об'єкта, створеного за цим класом. Що це означає? 10 нотаток про модифікатор Static у Java - 2 А те, що якщо змінна не статична, то в кожного нового об'єкта цього класу буде своє значення цієї змінної, змінюючи яке ми міняємо його виключно в одному об'єкті: Наприклад, у нас є клас Car з нестатичною змінною:

public class Car {
  int km;
}
Тоді в main:

Car orangeCar = new Car();
orangeCar.km = 100;

Car blueCar = new Car();
blueCar.km = 85;

System.out.println("Orange car - " + orangeCar.km);
System.out.println("Blue car - " + blueCar.km);
Виведення ми отримаємо:
Orange car - 100 Blue car - 85
Як бачимо, у кожного об'єкта своя змінна, зміна якої відбувається тільки для цього об'єкта. Ну а якщо у нас змінна статична, то це глобальне значення – одне для всіх: Тепер ми маємо Car зі статичною змінною:

public class Car {
  static int km;
}
Тоді той самий код у main видаватиме в консоль:
Orange car - 85 Blue car - 85
Адже змінна у нас одна на всіх, і щоразу ми змінюємо саме її. До статичних змінних зазвичай звертаються не за посиланням на об'єкт – orangeCar.km, а за ім'ям класу – Car.km

Статичний блок

Є два блоки ініціалізації – звичайний і статичний. Блок призначений для ініціалізації внутрішніх змінних. Якщо блок звичайний, то ним ініціалізують внутрішні змінні об'єкта, якщо ж статичний, відповідно, їм задають статичні змінні (тобто змінні класу). Приклад класу зі статичним блоком ініціалізації:

public class Car {
  static int km;
 
  static {
     km = 150;
  }
}

Статичний метод

Статичні методи відрізняються від звичайних тим, що вони також прив'язані до класу, а не до об'єкта. Важливою властивістю статичного методу є те, що він може звернутися тільки до статичних змінних/методів. Як приклад давайте розглянемо клас, який у нас буде таким собі лічильником, що веде облік викликів методу:

public class Counter {
  static int count;
 
  public static void invokeCounter() {
     count++;
     System.out.println("Поточне значення лічильника - " + count);
  }
}
Викличемо його в main:

Counter.invokeCounter();
Counter.invokeCounter();
Counter.invokeCounter();
І отримаємо виведення в консоль:
Поточне значення лічильника - 1 Поточне значення лічильника - 2 Поточне значення лічильника - 3

Статичний клас у Java

Статичним класом може бути тільки внутрішній клас. Знову ж таки, цей клас прив'язаний до зовнішнього класу, і якщо зовнішній успадковується іншим класом, то цей не буде успадкований. Водночас цей клас можна успадковувати, як і він може успадковуватися від будь-якого іншого класу та імплементувати інтерфейс. По суті, статичний вкладений клас нічим не відрізняється від будь-якого іншого внутрішнього класу за винятком того, що його об'єкт не містить посилання на об'єкт зовнішнього класу, який його створив. Проте завдяки цьому статичний клас найбільш схожий на звичайний не вкладений, адже єдина відмінність полягає в тому, що він упакований в інший клас. У деяких випадках для нас це перевага, оскільки з нього у нас є доступ до приватних статичних змінних зовнішнього класу. Приклад вкладеного статичного класу:

public class Vehicle {
 
  public static class Car {
     public int km;
  }
}
Створення екземпляра цього класу і задання значення внутрішньої змінної:

Vehicle.Car car = new Vehicle.Car();
car.km = 90;
Для використання статичних методів/змінних/класу нам не потрібно створювати об'єкт цього класу. Звісно, слід враховувати модифікатори доступу. Наприклад, поля private доступні тільки всередині класу, в якому вони оголошені. Поля protected доступні всім класам усередині пакета(package), а також усім класам-наслідникам поза пакетом. Припустимо, існує статичний метод increment() у класі Counter, завданням якого є інкрементування лічильника count. Для виклику цього методу можна використовувати звернення виду Counter.increment(). Немає необхідності створювати екземпляр класу Counter для доступу до статичного поля або методу. Це фундаментальна відмінність між статичними і не статичними об'єктами (членами класу). Ще раз нагадаю, що статичні члени класу безпосередньо належать класу, а не його екземпляру. Тобто значення статичної змінної count буде однакове для всіх об'єктів типу Counter. Далі в цій статті ми розглянемо засадничі аспекти застосування модифікатора static у Java, а також деякі особливості, які допоможуть зрозуміти ключові концепції програмування. 10 нотаток про модифікатор Static у Java - 3

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

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

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

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

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

  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". На практиці, будь-яке виключення, що виникло під час виконання та ініціалізації статичних полів, буде загорнуто Java в цю помилку. Це також найчастіша причина помилки "No Class Def Found Error", тому що клас не перебував у пам'яті під час звернення до нього.

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

  8. Важливою властивістю статичного блоку є ініціалізація. Статичні поля або змінні ініціалізуються після завантаження класу в пам'ять. Порядок ініціалізації зверху вниз, у тому ж порядку, в якому вони описані у вихідному файлі Java класу. Оскільки статичні поля ініціалізуються на потокобезпечний манер, ця властивість також використовується для реалізації патерну Singleton. Якщо ви не використовуєте список Enum як Singleton, з тих чи інших причин, то для вас є хороша альтернатива. Але в такому разі необхідно врахувати, що це не "лінива" ініціалізація. Це означає, що статичне поле буде проініціалізовано ще ДО того, як хтось про це «попросить». Якщо об'єкт ресурсоємний або рідко використовується, то ініціалізація його в статичному блоці зіграє не на вашу користь.

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

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

На цьому все. Усі перераховані вище пункти про модифікатор static у Java зобов'язаний знати кожен програміст. У цій статті було розглянуто базову інформацію про статичні змінні, поля, методи, блоки ініціалізації та імпорт. Зокрема деякі важливі властивості, знання яких є критичним під час написання та розуміння програм на Java. Я сподіваюся, що кожен розробник доведе свої навички використання статичних концептів до досконалості, оскільки це дуже важливо для серйозного програмування."
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ