Привет! Разберемся, что такое геттеры и сеттеры в Java и зачем они нужны. Коротко: геттер это public-метод, который возвращает значение приватного поля, а сеттер это public-метод, который задает полю значение и может проверить его на корректность. Поля класса закрывают модификатором private, а доступ к ним дают только через эти методы. Так работает инкапсуляция: данные защищены, а снаружи доступен только безопасный интерфейс. В прошлых лекциях ты уже научился создавать собственные полноценные классы, с полями и методами. Это серьезный прогресс, молодец! Но есть нюанс: до сих пор мы создавали классы не совсем правильно. Сейчас разберемся почему.

Кратко

  • Публичные поля (public) можно менять из любого места программы, в том числе задать им некорректные значения.
  • Решение: поля делаем private (закрываем), а доступ даем через public-методы.
  • Геттер (get) возвращает значение поля.
  • Сеттер (set) задает значение поля и может проверить его на корректность.
  • В сеттер можно встроить проверку (например, запретить отрицательный возраст).
  • Геттеры и сеттеры создают сразу, даже без проверок, чтобы потом не ломать чужой код.

В чем проблема с публичными полями?

Никаких ошибок, на первый взгляд, в этом классе нет:

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. 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 лет" и тому подобного).

Знакомься: геттеры и сеттеры

Список требований приличный! Но на самом деле все это легко достигается с помощью специальных методов: геттеров и сеттеров.
Геттеры и сеттеры дают контролируемый доступ к приватным полям
Название происходит от английского "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 плюс названия поля, за которое они отвечают. Разницу между геттером и сеттером удобно свести в таблицу:
ПризнакГеттер (get)Сеттер (set)
Назначениевернуть значение полязадать значение поля
Возвращаеттип поляничего (void)
Принимаетнет аргументовновое значение (аргумент)
Проверка данныхобычно нетможно встроить (например, запрет отрицательного возраста)
Пример имениgetName()setName(String name)

Как работают геттеры

Например, метод 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-доступ!
В такой ситуации лучше было бы скрыть поля и создать геттеры-сеттеры с самого начала. Все твои коллеги пользовались бы ими, и если бы тебя поздно "осенило", что надо ограничить значения полей, ты бы просто дописал проверку внутри сеттера. И ни у кого не сломался бы уже написанный код. Конечно, если ты хочешь, чтобы доступ к какому-то полю был только "на чтение", можно создать для него один геттер. "Снаружи", то есть за пределами твоего класса, должны быть доступны только методы. Данные должны быть скрыты.
Инкапсуляция: наружу открыты только методы, данные скрыты внутри
Можно привести аналогию с мобильным телефоном. Представь, что вместо обычного включенного мобильника тебе дали телефон со вскрытым корпусом, где все провода, схемы и т.д. торчат наружу. Телефон при этом работает: если сильно постараться и потыкаться в схемы, может даже получится совершить звонок. Но скорее ты его просто сломаешь. Вместо этого компания-производитель дает тебе интерфейс: клиент просто набирает нужные цифры, нажимает зеленую кнопку с трубкой, и звонок начинается. А уж что там происходит внутри со схемами и проводами и как они выполняют свою задачу, ему без разницы. В этом примере компания ограничила доступ к "внутренностям" (данным) телефона и оставила снаружи только интерфейс (методы). В итоге клиент получит то, что хотел (совершить звонок) и точно ничего не сломает внутри. Это и есть инкапсуляция, один из ключевых принципов ООП. Подробнее о модификаторах доступа в Java рассказано в документации Oracle (Java Tutorials).

Вопросы и ответы

Что такое геттеры и сеттеры в Java?

Геттер это public-метод, который возвращает значение приватного поля, а сеттер это public-метод, который задает полю значение. Через них дают контролируемый доступ к закрытым (private) полям объекта.

Зачем нужны геттеры и сеттеры, если есть публичные поля?

Публичное поле можно изменить откуда угодно и присвоить ему некорректное значение. Геттеры и сеттеры позволяют закрыть поля через private и контролировать доступ, а в сеттер встроить проверку значений (например, запрет отрицательного возраста).

Что такое инкапсуляция?

Это принцип ООП: скрыть внутренние данные объекта (поля) и оставить снаружи только методы. Данные защищены, а работать с объектом можно через безопасный интерфейс.

Как сделать поле доступным только для чтения?

Создать для него только геттер и не создавать сеттер. Тогда значение поля можно прочитать снаружи, но нельзя изменить.

Как называть геттеры и сеттеры?

Обычно это слово get или set плюс имя поля с большой буквы: для поля name это getName() и setName(). Геттер возвращает тип поля, а сеттер принимает значение как аргумент.

Почему геттеры и сеттеры советуют создавать сразу?

Если сначала сделать поля публичными, а потом закрыть их и добавить методы, весь код, который обращался к полям напрямую, сломается. Если создать методы сразу, проверки можно будет добавить позже, не ломая чужой код.

Читайте также