ООП ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-08-04

Геттеры и сеттеры в Java: концепция JavaBeans

Класс с двумя открытыми полями выглядит безобидно — до первого вызова вроде этого:

CircleWrong circle = new CircleWrong();
circle.diam = 25;
circle.radius = 10;   // компилятор молчит, объект в невозможном состоянии

Диаметр 25 при радиусе 10 — геометрически невозможно, но Java такое рассогласование не заметит: она проверяет типы, а не смысл. Именно от подобных молчаливых поломок инвариантов и защищают геттеры и сеттеры.

Геттер — это public метод, который возвращает значение поля класса. Сеттер — это public метод, который присваивает полю новое значение. Поля при этом объявляют с модификатором private, поэтому обратиться к ним напрямую извне класса нельзя — только через эти методы. Такой класс, где приватные поля закрыты парами get/set, называют JavaBean.

Что такое геттеры и сеттеры

Если поле объявлено как private, оно видно только внутри своего класса. Но данные объекта всё равно нужно читать и изменять снаружи, иначе такое поле бессмысленно. Поэтому доступ открывают через специальные public методы:

  • геттер (getter, метод доступа) — возвращает значение поля;
  • сеттер (setter, метод изменения) — записывает в поле новое значение.

Поле, к которому есть такая пара методов, называют свойством (property) класса. Важно, что имя свойства определяется именно именами методов, а не именем поля: для фреймворков вроде Jackson или Hibernate класс — это набор его геттеров и сеттеров, а как называется поле внутри, они не знают.

Правила именования: get, set и is

Имя метода строится по строгой схеме: берётся имя свойства, его первая буква переводится в верхний регистр и к результату добавляется префикс.

Тип свойства Префикс геттера Префикс сеттера Пример поля Пример методов
Любой, кроме boolean get set name getName() / setName(String)
boolean (примитив) is или get set printed isPrinted() / setPrinted(boolean)
Boolean (обёртка) только get set active getActive() / setActive(Boolean)
Коллекция get set roles getRoles() / setRoles(List<String>)

Требования к сигнатурам:

  1. Геттер объявляется как public, не принимает параметров и возвращает значение типа свойства.
  2. Сеттер объявляется как public, возвращает void и принимает ровно один параметр типа свойства.
public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

Ключевое слово this в сеттере обязательно: параметр age перекрывает поле age, и без this. строка age = age присвоит параметр самому себе, а поле останется нулевым. Компилятор такую строку пропустит — предупреждение выдаст только IDE или статический анализатор.

Важно

Префикс is разрешён спецификацией JavaBeans только для примитивного boolean. Если поле имеет тип обёртки Boolean, метод isActive() стандартный интроспектор свойством не признает — нужен getActive(). Именно поэтому свойство иногда «пропадает» из JSON после замены boolean на Boolean ради поддержки null.

Концепция JavaBeans: требования к классу

JavaBean — это обычный Java-класс, оформленный по соглашению, которое позволяет внешним инструментам работать с ним, ничего не зная о его устройстве. Соглашение состоит из четырёх пунктов.

Требование Зачем нужно Насколько строго на практике
public класс Чтобы фреймворк мог его загрузить Обязательно
public конструктор без аргументов Фреймворк создаёт экземпляр через рефлексию, не зная про параметры Обязательно для Hibernate и для десериализации Jackson по умолчанию
Поля private (или protected) Инкапсуляция: состояние меняется только через методы класса Соглашение, но нарушать его смысла нет
Геттеры и сеттеры по правилам именования По ним вычисляется имя свойства Обязательно
implements Serializable Сохранение состояния объекта в поток По спецификации требуется, в веб-разработке почти не используется

На это соглашение опирается практически весь Java-экосистемный инструментарий: Jackson и Gson при работе с JSON, JPA/Hibernate при отображении объектов на таблицы БД, Spring при внедрении зависимостей через сеттеры, JSP и Expression Language при обращении ${person.fullName}, JavaFX при привязке свойств к элементам интерфейса. Подробности описаны в документации класса Introspector.

Пример: класс Person

Класс Person с тремя приватными полями и полным набором методов доступа. Обратите внимание на isRetired(): поле имеет примитивный тип boolean, поэтому префикс is здесь допустим.

import java.io.Serializable;

public class Person implements Serializable {
    private String fullName;
    private int age;
    private boolean retired;

    public Person() {
    }

    public Person(String fullName, int age, boolean retired) {
        this.fullName = fullName;
        this.age = age;
        this.retired = retired;
    }

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public boolean isRetired() {
        return retired;
    }

    public void setRetired(boolean retired) {
        this.retired = retired;
    }
}
public class PersonExample {
    public static void main(String[] args) {
        Person person = new Person();
        person.setFullName("Петров Иван Иванович");
        person.setAge(56);
        person.setRetired(false);

        System.out.println("Полное имя: " + person.getFullName());
        System.out.println("Возраст: " + person.getAge());
        System.out.println("Пенсионер? " + person.isRetired());
    }
}
Полное имя: Петров Иван Иванович
Возраст: 56
Пенсионер? false

Для чего нужны геттеры и сеттеры

Геттеры и сеттеры делают код многословнее, поэтому вопрос «зачем они, если можно объявить поле public» возникает у всех. Ответ виден на примере класса, где два поля связаны между собой.

public class CircleWrong {
    int radius;   // модификатор не указан: поле видно всему пакету
    int diam;
}

Любой класс из того же пакета может изменить эти поля напрямую. Значения должны быть согласованы — диаметр вдвое больше радиуса, — но ничто этого не гарантирует:

public class CircleWrongExample {
    public static void main(String[] args) {
        CircleWrong circle = new CircleWrong();
        circle.diam = 25;
        circle.radius = 10;

        System.out.println("Диаметр: " + circle.diam);   // 25
        System.out.println("Радиус: " + circle.radius);  // 10  — противоречие
    }
}

Объект хранит бессмысленную комбинацию, и все дальнейшие вычисления по нему будут неверными. Причём ошибка проявится не здесь, а где-нибудь в отчёте через три модуля — искать её придётся долго.

Перепишем класс по концепции JavaBeans и добавим в сеттеры логику: каждый из них пересчитывает второе поле, а заодно отсекает отрицательные значения.

public class Circle {
    private double radius;
    private double diam;

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        if (radius < 0) {
            throw new IllegalArgumentException("Радиус не может быть отрицательным: " + radius);
        }
        this.radius = radius;
        this.diam = radius * 2;
    }

    public double getDiam() {
        return diam;
    }

    public void setDiam(double diam) {
        if (diam < 0) {
            throw new IllegalArgumentException("Диаметр не может быть отрицательным: " + diam);
        }
        this.diam = diam;
        this.radius = diam / 2;
    }
}
public class CircleExample {
    public static void main(String[] args) {
        Circle circle = new Circle();
        circle.setDiam(25);

        System.out.println(circle.getDiam());    // 25.0
        System.out.println(circle.getRadius());  // 12.5 — согласовано
    }
}

Теперь состояние объекта нельзя привести в противоречивый вид: какой бы сеттер ни вызвали, второе поле пересчитается автоматически. Это и есть главный смысл сеттера — не «присвоить значение», а сохранить инвариант класса.

Неочевидный момент

Здесь поля намеренно имеют тип double, а не int. С типом int вызов setDiam(25) выполнил бы целочисленное деление 25 / 2 и записал в радиус 12, после чего radius * 2 дало бы 24 при диаметре 25 — инвариант нарушен снова, только теперь тише и незаметнее. Сеттер защищает данные лишь настолько, насколько корректна арифметика внутри него.

Логика внутри геттера

Геттер тоже не обязан быть однострочником return field;. Он может отдавать вычисленное или урезанное представление данных. Классический пример — пароль, который нельзя показывать целиком: возвращаем первый символ, остальное заменяем звёздочками.

public class User {
    private String login;
    private String password;

    public User(String login, String password) {
        this.login = login;
        this.password = password;
    }

    public String getLogin() {
        return login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    public String getMaskedPassword() {
        if (password == null || password.isEmpty()) {
            return "";
        }
        return password.charAt(0) + "*****";
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
public class UserExample {
    public static void main(String[] args) {
        User user = new User("mylogin", "mypassword");
        System.out.println("Логин: " + user.getLogin());
        System.out.println("Пароль: " + user.getMaskedPassword());
    }
}
Логин: mylogin
Пароль: m*****

Почему метод называется getMaskedPassword, а не getPassword

Геттер обязан возвращать значение свойства. Если назвать маскирующий метод getPassword(), любой инструмент, работающий по соглашению JavaBeans, решит, что свойство password действительно равно m*****: Jackson положит эту строку в JSON, а Hibernate запишет её в базу вместо настоящего пароля. Маскирование — это отдельная операция, и имя метода должно об этом говорить.

IDE и Lombok: не писать это руками

Методы доступа никто не набирает вручную. В IntelliJ IDEA их генерирует Alt + InsertGetter and Setter, в Eclipse — SourceGenerate Getters and Setters. IDE сама расставит префиксы, включая is для примитивного boolean.

Второй вариант — библиотека Lombok, которая генерирует методы на этапе компиляции по аннотациям:

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class Person {
    private String fullName;
    private int age;
    private boolean retired;
}

В байт-коде появятся getFullName(), setFullName(String), isRetired() и остальные — обращаться к ним можно как к обычным методам. Аннотации можно ставить и над отдельным полем, если нужен, например, только геттер.

Аннотация Lombok Что генерирует Когда применять
@Getter / @Setter Методы доступа для всех полей класса или для одного поля Базовый вариант, полный контроль над тем, что открыто
@Data @Getter, @Setter, toString(), equals(), hashCode() и конструктор для обязательных полей Простые DTO. Для JPA-сущностей нежелательно: сгенерированные equals/hashCode ломаются на ленивых связях
@Value Неизменяемый класс: поля final, только геттеры Объекты-значения там, где недоступны records

Records вместо JavaBeans

Начиная с Java 16, для классов, единственная задача которых — хранить данные, есть record:

public record Person(String fullName, int age, boolean retired) {
}

Компилятор сам создаёт приватные final поля, конструктор со всеми параметрами, equals(), hashCode() и toString(), а также методы доступа. Но обращение к ним выглядит иначе:

Person person = new Person("Петров Иван Иванович", 56, false);
System.out.println(person.fullName());  // не getFullName()
System.out.println(person.retired());   // не isRetired()
Признак JavaBean Record
Имя метода чтения getFullName(), isRetired() fullName(), retired()
Изменяемость Изменяемый: есть сеттеры Неизменяемый: сеттеров нет в принципе
Конструктор без аргументов Есть Невозможен
Наследование Можно расширять final, наследование запрещено
Где уместен Сущности JPA, формы, объекты с изменяемым состоянием DTO, ответы API, ключи, результаты запросов

Формально record не является JavaBean: у него нет ни сеттеров, ни конструктора без аргументов, а имена методов не соответствуют соглашению. Современные библиотеки научились с этим работать напрямую (Jackson поддерживает records с версии 2.12), а вот Hibernate использовать record как сущность не позволяет — там по-прежнему нужен изменяемый класс с конструктором без аргументов. Подробности — в JEP 395.

На чём чаще всего ошибаются

  1. Геттер возвращает изменяемую коллекцию. Метод getRoles(), отдающий сам List, полностью сводит инкапсуляцию на нет: вызывающий код может сделать person.getRoles().clear() и опустошить внутреннее состояние объекта. Возвращайте List.copyOf(roles) или Collections.unmodifiableList(roles).
  2. Поле начинается с одной строчной буквы перед заглавной. Для поля sName IDE сгенерирует getsName(), и стандартный интроспектор увидит свойство sName вместо ожидаемого. Соглашение декапитализации: если первые два символа имени метода после префикса — заглавные (getURL()), имя свойства остаётся как есть — URL; во всех остальных случаях первая буква переводится в нижний регистр.
  3. Сеттер, который только присваивает. Пара пустых get/set над каждым полем ничем не лучше public поля — только длиннее. Смысл появляется, когда внутри есть валидация, пересчёт связанных полей или защитное копирование. Если для поля не нужно ни того, ни другого и меняться оно не должно — сеттер не пишите вовсе.
  4. Вызов сеттера из конструктора. Если наследник переопределит сеттер, он выполнится до инициализации полей подкласса и увидит их со значениями по умолчанию. В конструкторе присваивайте полям значения напрямую.
  5. Забытый конструктор без аргументов. Как только в классе появляется конструктор с параметрами, конструктор по умолчанию исчезает — и Hibernate падает с InstantiationException, а Jackson с InvalidDefinitionException. Объявляйте его явно, как в примере с Person.

Часто задаваемые вопросы

Нужно ли писать геттер и сеттер для каждого поля?

Нет. Методы доступа создают только для тех свойств, которые действительно должны быть видны или изменяемы снаружи. Для поля, которое задаётся один раз в конструкторе и дальше не меняется, пишут только геттер — получается свойство «только для чтения». А для чисто служебных полей, например кэшированного значения или счётчика попыток, не нужен ни геттер, ни сеттер.

Почему префикс is не работает с типом Boolean?

Спецификация JavaBeans разрешает префикс is только для примитивного типа boolean. Класс java.beans.Introspector, на который опираются JSP EL, JavaFX и часть библиотек, метод isActive() с возвращаемым типом Boolean свойством не считает. Поэтому после замены boolean на Boolean ради поддержки null свойство может исчезнуть из JSON или из EL-выражения. Правильное имя в этом случае — getActive().

Чем record отличается от JavaBean и что выбрать?

Record неизменяем, его методы доступа называются по имени компонента без префикса get, у него нет сеттеров и нет конструктора без аргументов. Выбирайте record для DTO, ответов API и любых объектов, состояние которых не меняется после создания. JavaBean с геттерами и сеттерами остаётся нужен там, где объект должен изменяться или где этого требует фреймворк — прежде всего для сущностей Hibernate и JPA.

Может ли геттер нарушить инкапсуляцию?

Да, и это частый вопрос на собеседовании. Если геттер возвращает ссылку на изменяемый объект — список, массив, Date — вызывающий код получает доступ к внутреннему состоянию и может его изменить: вызов getRoles().add("ADMIN") добавит роль в обход всякой проверки. Решение — возвращать защитную копию через List.copyOf или неизменяемую обёртку Collections.unmodifiableList.

Зачем JavaBean нужен конструктор без аргументов?

Фреймворк создаёт объект через рефлексию и не знает, какие параметры принимает ваш конструктор. Он вызывает конструктор без аргументов, а затем заполняет свойства сеттерами. Так работают Hibernate при загрузке сущности из базы и Jackson при разборе JSON в объект. Если в классе объявлен только конструктор с параметрами, конструктор по умолчанию не создаётся и приложение падает уже во время выполнения.

Видео объяснение

Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.

Комментарии

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.