1. Властивості: гетер і сетер

Коли великий проєкт розробляють десятки програмістів одночасно, нерідко виникають проблеми з тим, що програмісти по-різному поводяться з даними, які зберігаються в полях класу.

Ніхто докладно не вивчає документацію щодо класів, або в ній може бути описано не всі випадки, тому часто трапляються ситуації, коли дані всередині об'єкта «псуються» і об'єкт стає неприпустимим.

Щоб уникнути таких ситуацій, у Java прийнято всі поля класу робити приватними (private). Тільки методи класу можуть змінювати змінні класу, і ніякі методи з інших класів не мають безпосереднього доступу до змінних класу. Отак.

Якщо ви хочете, щоб інші класи могли отримувати або змінювати дані всередині об'єктів вашого класу, потрібно додати в код вашого класу два методи — get-метод і set-метод. Приклад:

Код Примітка
class Person
{
   private String name;

   public Person(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return name;
   }

   public void setName(String name)
   {
      this.name = name;
   }
}


private-поле name



Ініціалізація поля за допомогою конструктора


getName() — метод повертає значення поля name




setName() — метод змінює значення поля name

Жоден інший клас не зможе безпосередньо змінити значення поля name. Якщо комусь потрібно отримати значення поля name, доведеться викликати метод getName() для об'єкта типу Person. Якщо хочеться поміняти в коді значення поля name, потрібно буде викликати метод setName() для об'єкта типу Person.

Метод getName() інакше називають «гетер поля name», а  метод setName() — «сетер поля name».

Це дуже поширений підхід. У 80–90 % всього Java-коду ви ніколи не побачите публічних змінних класу. Натомість вони будуть оголошені private (або protected), і кожна змінна матиме публічні гетери й сетери.

З таким підходом код стає довшим, але надійнішим.

Безпосереднє звернення до змінної класу — це як поворот через подвійну суцільну лінію: простіше та швидше, але якщо так робитимуть усі, то всім буде від цього тільки гірше.

Припустімо, ви хочете створити клас, який описує точку з координатами x і y на площині. Отак це зробив би програміст-початківець:

class Point
{
   public int x;
   public int y;
}

А отак це зробив би досвідчений Java-програміст:

Код
class Point {
   private int x;
   private int y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }

   public int getX() {
      return x;
   }

   public void setX(int x) {
      this.x = x;
   }

   public int getY() {
      return y;
   }

   public void setY(int y) {
      this.y = y;
   }
}

Код став довшим? Безумовно.

Натомість у сетери й гетери можна додати валідацію параметрів. Наприклад, можна слідкувати за тим, щоб x та y завжди були більшими за нуль (або не меншими за нуль). Приклад:

Код Примітка
class Point {
   private int x;
   private int y;

   public Point(int x, int y) {
      this.x = x < 0 ? 0 : x;
      this.y = y < 0 ? 0 : y;
   }

   public int getX() {
      return x;
   }

   public void setX(int x) {
      this.x = x < 0 ?  0 : x;
   }

   public int getY() {
      return y;
   }

   public void setY(int y) {
      this.y = y < 0 ? 0 : y;
   }
}

8
Задача
Модуль 1. Java Syntax,  16 рівень2 лекція
Недоступна
Бухгалтерія
У класі Solution є список працівників waitingEmployees, які чекають на зарплату, і список працівників alreadyGotSalaryEmployees, які вже її отримали. Потрібно реалізувати метод paySalary(String). Як аргумент у метод слід передати ім'я працівника, котрий бажає отримати зарплату. Метод має перевірити
8
Задача
Модуль 1. Java Syntax,  16 рівень2 лекція
Недоступна
Перевертання даних
Ти бачиш робочу програму, в якій масив int[] numbers заповнюється числами в методі init(). Потім у методі reverse() відбувається переставляння чисел у зворотному порядку. Твоє завдання — переписати код так, щоб замість масиву int[] numbers використовувався список ArrayList<Integer> numbers. Ім

2. Тривалість життя об'єкта

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

У багатьох мовах програмування, як-от у С++, для видалення об'єкта є спеціальний оператор delete. А як вирішується це питання в Java?

У мові Java все влаштовано трохи по-іншому, і оператора delete у Java немає. Чи означає це, що об'єкти в Java не видаляються? Ні, звичайно ж, видаляються. Інакше в Java-програмах швидко закінчилася би пам'ять, і про місяці безперервної роботи навіть і не йшлося б.

У мові Java процес видалення об'єктів повністю автоматизовано — видаленням об'єктів займається сама Java-машина. Такий процес називають збиранням сміття (garbage collecting), а механізм, який збирає сміття, — збирачем сміттяGarbage Collector або скорочено GC.

Тож як Java-машина дізнається, що якийсь об'єкт потрібно видалити й коли?

Усі об'єкти збирач сміття розділяє на досяжні й недосяжні. Якщо на об'єкт є щонайменше одне посилання, він вважається досяжним. Якщо немає жодної змінної, що посилається на об'єкт, такий об'єкт вважається недосяжним і оголошується сміттям: значить, його можна видаляти.

У Java не можна взяти й створити посилання на наявний об'єкт: його можна тільки присвоїти. Якщо ми видалили всі посилання на об'єкт, його втрачено назавжди.

Циклічні посилання

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

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

Саме тому збирач сміття розділяє об'єкти не на «об'єкти з посиланнями» і «об'єкти без посилань», а на досяжні й недосяжні.

Досяжні об'єкти

Спочатку до списку досяжних додаються об'єкти, які на 100 % «живі». Наприклад, поточна нитка (Thread.current()) або Консоль (System.in).

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

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


3. Збирання сміття

Фрагментація пам'яті

Ще один важливий момент, пов'язаний з видаленням об'єктів, — фрагментація пам'яті. Якщо постійно створювати й видаляти об'єкти, скоро в пам'яті настане розгардіяш: заповнені ділянки пам'яті будуть увесь час перемежатися з вільними.

І запросто може трапитися ситуація, коли ми не зможемо створити великий об'єкт (наприклад, масив на мільйон елементів) через відсутність великого шматка вільної пам'яті. Тобто вільна пам'ять нібито і є, і її багато, а от великого цільного шматка вільної пам'яті може й не бути.

Оптимізація (дефрагментація) пам'яті

Java-машина вирішує цю проблему специфічним чином. Приблизно отак:

Пам'ять розділяється на дві частини. Усі об'єкти створюються (і видаляються) лише в одній її половині. Коли настає час усунути «дірки» в пам'яті, всі об'єкти з першої половини копіюються у другу половину. Однак копіюються вони вже впритул один до одного, щоб «дірок» не було.

Цей процес має приблизно такий вигляд:

Етап 1: Після створення об'єктів

Збирання сміття в Java

Етап 2: Виникнення «дірок»

Збирання сміття в Java

Етап 3: Усунення «дірок»

Збирання сміття в Java

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