JavaRush /Java блог /Java Developer /Геттери і сеттери
Автор
Василий Малик
Senior Java-разработчик в CodeGym

Геттери і сеттери

Стаття з групи Java Developer
Привіт! У попередніх лекціях ми вже навчилися створювати власні повноцінні класи, з полями і методами. Це серйозний прогрес, молодець! Але нині змушений повідомити тобі непрємну правду. Ми створювали наші класи не зовсім правильно! Чому? Жодних помилок, на перший погляд, в цьому класі нема:

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("Няв!");
   }
}
А насправді — є. Уяви, що ти на роботі написав ось такий клас Cat, що означає котів, і пішов додому. Поки тебе не було, інший програміст прийшов на роботу, створив свій клас Main, де почав використовувати клас, який ти написав — Cat.

public class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();
       cat.name = "";
       cat.age = -1000;
       cat.weight = 0;
   }
}
Не суттєво, для чого він це зробив і як так сталося: може, втомилася людина, або не виспалася. Важливо інше: наш поточний клас Cat дозволяє присвоювати полям неможливі значення. В результаті в програмі знаходяться об'єкти з некоректним станом, по типу ось цієї кішки з віком -1000 років. Якої ж помилки ми припустилися? Під час створення класу ми відкрили його дані. Поля name, age і weight знаходяться в публічному доступі. До них можна звернутися в будь-якому місці програми: достатньо просто створити об'єкт Cat — і все, у будь-якого програміста є доступ до його даних напряму через оператор “.

Cat cat = new Cat();
cat.name = "";
Тут ми напряму отримуємо доступ до поля name та встановлюємо для нього значення. Нам потрібно якось захистити наші дані від некоректного втручання ззовні. Що ж для цього потрібно? По-перше, всі змінні екземпляра (поля) необхідно позначати модифікатором private. Private — найсуворіший модифікатор доступу в Java. Якщо ти його використовуєш, поля класу Cat не будуть доступними за його межами.

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 class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();
       cat.name = "";//помилка! Поле name у класі Cat має private-доступ!
   }
}
Компілятор це бачить і одразу видає помилку. Тепер поля ніби захищені. Але виходить, що доступ до них закрито “намертво”: у програмі не можна навіть отримати вагу існуючої кішки, якщо це знадобиться. Це теж не варіант: у такому вигляді наш клас практично неможливо використовувати. В ідеалі нам потрібно дозволити якийсь обмежений доступ до даних:
  • Інші програмісти повинні мати можливість створювати об'єкти Cat
  • У них має бути можливість зчитувати дані з об'єктів, які вже існують (наприклад, отримати ім'я або вік кішки, яка вже існує)
  • Також повинна бути можливість присвоювати значення полів. Але при цьому — лише коректні значення. Від неправильних наші об'єкти мають бути захищені (жодного “вік = -1000 років” тощо).
Список вимог суттєвий! Але насправді цього легко досягнути за допомогою спеціальних методів — геттерів і сеттерів.
Геттери і сеттери - 2
Назва походить від англійського “get” — “отримувати” (тобто “метод для отримання значення поля”) і set — “встановлювати” (тобто “метод для встановлення значення поля”). Давай подивимося, як вони виглядають на прикладі нашого класу Cat:

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;
   }
}
Як бачиш, усе доволі просто :) Їх назви частіше за все складаються зі слова get/set + назви поля, за яке вони відповідають. Наприклад, метод getWeight() повертає значення поля weight у того об'єкта, для якого його викликали. Ось як це виглядає в програмі:

public class Main {

   public static void main(String[] args) {

       Cat barsik = new Cat("Барсік", 5, 4);
       String barsikName = barsik.getName();
       int barsikAge = barsik.getAge();
       int barsikWeight = barsik.getWeight();

       System.out.println("Ім'я кота: " + barsikName);
       System.out.println("Вік кота: " + barsikAge);
       System.out.println("Вага кота: " + barsikWeight);
   }
}
Виведення в консолі:

Ім'я кота: Барсік
Вік кота: 5
Вага кота: 4
Тепер із іншого класу (Main) є доступ до полів Cat, але лише через геттери. Зверни увагу: у геттерів стоїть модифікатор доступу public, але є вони доступні з будь-якої точки програми. А як усе влаштовано у присвоєнні значень? За це відповідають методи-сеттери

public void setName(String name) {
   this.name = name;
}
Їх робота, як бачиш, теж проста. Ми викликаємо метод setName() в об'єкта Cat, передаємо йому як аргумент рядок, і цей рядок присвоюється в поле name нашого об'єкта.

public class Main {

   public static void main(String[] args) {

       Cat barsik = new Cat("Барсік", 5, 4);

       System.out.println("Початкове ім'я кота — " + barsik.getName());
       barsik.setName("Василь");
       System.out.println("Нове ім'я кота — " + barsik.getName());
   }
}
Тут ми використовували і геттери, і сеттери. Спочатку за допомогою геттера ми отримали і вивели в консолі початкове ім'я кота. Потім за допомогою сеттера призначили його полю name нове значення — “Василь”. І потім за допомогою геттера отримали ім'я знову, щоб перевірити, чи дійсно воно змінилося. Виведення в консолі:

Початкове ім'я кота — Барсік
Нове ім'я кота — Василь
Здавалося б, у чому різниця? Ми також можемо присвоювати полям об'єкта некоректні значення, навіть якщо в нас є сеттери:

public class Main {

   public static void main(String[] args) {

       Cat barsik = new Cat("Барсик", 5, 4);
       barsik.setAge(-1000);

       System.out.println("Вік Барсіка — " + barsik.getAge() + " років");
   }
}
Виведення в консолі:

Вік Барсіка — -1000 років
Різниця в тому, що сеттер — це повноцінний метод. А в методі, на відміну від поля, ти можеш закласти необхідну тобі логіку перевірки, щоб не допустити неприйнятних значень. Наприклад, можна легко не дозволити призначення від'ємного числа як віку:

public void setAge(int age) {
   if (age >= 0) {
       this.age = age;
   } else {
       System.out.println("Помилка! Вік не може бути від'ємним числом!");
   }
}
І тепер наш код працює коректно!

public class Main {

   public static void main(String[] args) {

       Cat barsik = new Cat("Барсік", 5, 4);
       barsik.setAge(-1000);

       System.out.println("Вік Барсіка — " + barsik.getAge() + " років");
   }
}
Виведення в консолі:

Помилка! Вік не може бути від'ємним числом!
Вік Барсіка — 5 років
Всередині сеттера є обмеження, і воно захищає від спроби встановити некоректні дані. Вік Барсіка залишився без змін. Геттери і сеттери потрібно створювати завжди. Навіть якщо в твоїх полях нема обмеженб на можливі значення, шкоди від них не буде. Уяви собі ситуацію: ти й твої колеги разом пишете програму. Ти створюєш клас Cat з публічними полями, і все програмісти користуються ними як завгодно. І тут в один прекрасний день ти усвідомлюєш: “Рано чи пізно хтось може випадково присвоїти від'ємне число до змінної weight! Потрібно створити сеттери і зробити всі поля приватними!” Ти їх створюєш, і весь код, який написали твої колеги, тої ж миті ламається. Адже вони вже написали купу коду, де зверталися до полів Cat напряму.

cat.name = "Бегемот";
А тепер поля стали приватними, і компілятор видає купу помилок!

cat.name = "Бегемот";//помилка! Поле name класу Cat має private-доступ!
У такому випадку краще було б приховати поля і створити геттери-сеттери з самого початку. Усі твої колеги користувалися б ними, і якщо б тебе пізно “осяяло”, що потрібно обмежити значення полів, тобі просто було б потрібно дописати перевірку всередині сеттера. І ні в кого не зламався би код, який вже написано. Звісно, якщо ти хочешь, щоб доступ до якогось поля був лише “на читання”, можна створити для нього один геттер. “Зовні”, тобто за межами твого класу, мають бути доступними лише методи. Дані потрібно приховати.
Геттери і сеттери - 4
Можна навести аналогію з мобільним телефоном. Уяви, що замість звичайного ввімкненого мобільника тобі дали телефон з розкритим корпусом, де всі дроти, схеми та ін. стирчать назовні. Телефон тим часом працює: якщо докласти зусиль і потикати схеми, може навіть вдасться подзвонити. Але скоріш за все ти його просто зламаєш. Замість цього компанія-виробник надає тобі інтерфейс: клієнт просто набирає потрібні цифри, натискає зелену кнопку з трубкою — і починається дзвінок. А що там відбувається всередині зі схемами й дротами і як вони виконують свої завдання, клієнту байдуже. У цьому прикладі компанія обмежила доступ до “нутрощів” (даних) телефону і залишила ззовні лише інтерфейс (методи). Отже, клієнт отримає те, що хотів (здійснити дзвінок) і точно нічого не зламає всередині.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ