Как написать геттер

Геттеры и сеттеры широко используются в программировании на Java. Однако не каждый программист понимает и реализует эти методы должным образом. В этой статье мы подробно раскроем данную тему.

1. Что такое геттеры и сеттеры?

В Java геттер и сеттер — это два обычных метода, которые используются для получения значения поля класса или его изменения.

Следующий код является примером простого класса с private-переменной и реализованных, для доступа к ней извне, геттера и сеттера:


public class SimpleGetterSetter {
    private int number;
 
    public int getNumber() {
        return number;
    }
 
    public void setNumber(int number) {
        this.number = number;
    }
}

Поскольку number является private, то обратиться к ней напрямую за пределами данного класса не получится:


SimpleGetterSetter obj = new SimpleGetterSetter();

// возникнет ошибка компиляции, т.к. number является private
obj.number = 10;   

// то же самое
int number = obj.number;

Чтобы таких проблем не было, внешний код должен вызывать геттер getNumber() и сеттер setNumber(), чтобы получить или обновить значение переменной:


SimpleGetterSetter obj = new SimpleGetterSetter();
 
obj.setNumber(10);  // ок
int number = obj.getNumber();  // ок

Итак, сеттер — это метод, который изменяет (устанавливает; от слова set) значение поля. А геттер — это метод, который возвращает (от слова get) нам значение какого-то поля.

Геттер иногда называют accessor (аксессор, т.к. он предоставляет доступ к полю), а сеттер mutator (мутатор, т.к. он меняет значение переменной).

2. Зачем нужны геттеры и сеттеры?

Представьте ситуацию, когда нам необходимо изменить состояние объекта (значение его полей) на основе некоторого условия. Как мы могли бы достичь этого без сеттера:

  • сделать поля public, protected или default
  • изменять их значения с помощью оператора точки (.)

Давайте посмотрим на последствия этих действий:

Во-первых, сделав поля public, protected или default, мы теряем контроль над данными и ставим под угрозу один из основополагающих принципов ООП — инкапсуляцию.

Во-вторых, поскольку поля не private, то кто угодно может изменить их за пределами класса. Это значит, что мы не сможем добиться их неизменяемости.

В-третьих, мы не можем предоставить никакой логики для изменения полей по каким-то условиям.

Давайте предположим, что у нас есть класс Employee с полем retirementAge:


public class Employee {
    public String name;
    public int retirementAge;

    public Employee(String name, int retirementAge) {
        this.name = name;
        this.retirementAge = retirementAge;
    }
}

Обратите внимание, что в классе мы установили поля как public, чтобы разрешить к ним доступ извне. Теперь давайте изменим retirementAge сотрудника:


public class RetirementAgeModifier {

    public static void main(String[] args) {
        Employee employee = new Employee("John", 58);
        employee.retirementAge = 18;
    }
}

Как мы видим, любой, кто обратится к классу Employee, может легко делать то, что он хочет с полем retirementAge. Нет никакого способа проверить корректность этого изменения.

В-четвертых, а как бы мы могли ограничить доступ к полям только для чтения или только для записи извне?

Вот тут и появляются геттеры и сеттеры.

Используя геттер и сеттер, мы можем контролировать доступ к важным переменным и их обновление (например, требуется изменить значение переменной в заданном диапазоне. В противном случае новое значение не будет присвоено).

Рассмотрим следующий код сеттера:


public void setNumber(int number) {
    if (number < 10 || number > 100) {
        throw new IllegalArgumentException();
    }
    this.number = number;
}

Этот код гарантирует, что значение number всегда будет находиться в диапазоне от 10 до 100. Без наличия подобного сеттера пользователь может установить для number любое значение:

Такие действия будут нарушать допустимое ограничение для значений этой переменной, что нас, конечно, не устраивает. Таким образом, установка переменной number как private и использование сеттера, устраняют все перечисленные ранее проблемы.

Что касается геттера, то он является единственным безопасным способом получения извне значения переменной number:


public int getNumber() {
    return number;
}

Следующая схема поясняет всю ситуацию:

Геттеры и сеттеры защищают значение переменной от неожиданных изменений.

Когда переменная скрыта модификатором private и доступна только через геттер и сеттер, она инкапсулирована. Поэтому реализация геттеров и сеттеров является одним из способов обеспечения инкапсуляции в коде программы.

Некоторые фреймворки такие, как Hibernate, Spring, Struts и т. д. могут проверять информацию или внедрять свой служебный код через геттер и сеттер. Поэтому при интеграции вашего кода с ними необходимо иметь эти методы.

Подытоживая, можно выделить ряд плюсов при использовании геттеров и сеттеров:

  • они помогают достичь инкапсуляции для скрытия состояния объекта и предотвращения прямого доступа к его полям
  • при реализации только геттера (без сеттера) можно достичь неизменяемости объекта
  • они могут предоставлять дополнительные функции: проверка корректности значения перед его присваиванием полю или обработка ошибок. Таким образом, мы можем добавить условную логику и обеспечить поведение в соответствии с потребностями (если сеттер не имеет подобной логики, а лишь присваивает полю какое-то значение, то его наличие не обеспечивает инкапсуляцию. А его присутствие становится фиктивным)
  • можем предоставить полям разные уровни доступа: например, get (доступ для чтения) может быть public, в то время как set (доступ для записи) может быть protected
  • с их помощью мы достигаем еще одного ключевого принципа ООП — абстракции, которая скрывает детали реализации, чтобы никто не мог использовать поля непосредственно в других классах или модулях

3. Правила именования геттеров и сеттеров

Принцип именования геттеров и сеттеров должен соответствовать конвенции Java об именовании: getXXX() и setXXX(),

где ХХХ — это имя переменной для которой реализуются эти методы. Например, для следующей переменной:

имена геттера и сеттера будут такие:


public void setName(String name)
 
public String getName()

Если переменная имеет тип boolean, то геттер будет содержать префикс isXXX(). Например:


private boolean single;
 
public String isSingle()

public void setSingle(boolean single)

Следующая таблица показывает примеры названий геттеров и сеттеров с учетом конвенции об их именовании:

4. Типичные ошибки при использовании геттеров и сеттеров

Разработчики, так же как и обычные люди, часто делают ошибки. Эта глава описывает наиболее типичные ошибки при реализации геттеров и сеттеров, а также пути их устранения:

Ошибка № 1: Использование геттеров и сеттеров для public-полей.

Рассмотрим следующий фрагмент кода:


public String firstName;
 
public void setFirstName(String firstName) {
    this.firstName = firstName;
}
 
public String getFirstName() {
    return firstName;
}

Переменная firstName объявлена с модификатором public, следовательно, доступ к ней может быть получен напрямую, делая геттер и сеттер бесполезными. Для решения этой проблемы необходимо использовать модификатор с более ограниченным доступом, как protected или private:


private String firstName;

В третьем издании книги Джошуа Блоха «Java. Эффективное программирование» на эту проблему указано в главе 4.2: «Используйте в открытых классах методы доступа, а не открытые поля».

Ошибка № 2: Присваивание ссылки на объект напрямую в сеттере.


private int[] scores;
 
public void setScores(int[] scores) {
    this.scores = scores;
}

Ниже приведен код, демонстрирующий проблему:


int[] myScores = {5, 5, 4, 3, 2, 4}; // 1
 
setScores(myScores); // 2
displayScores(); // 3   
 
myScores[1] = 1; // 4
displayScores(); // 5

Массив целых чисел myScores, проинициализированный шестью значениями (строка 1), передается в метод setScores() (строка 2). Затем метод displayScores() выводит все значения этого массива:


public void displayScores() {
    for (int number : scores) {
        System.out.print(number + " ");
    }
    System.out.println();
}

В консоли отобразится следующий результат:

Строка myScores[1] = 1; меняет значение 2-го элемента (не забываем, что в массивах нумерация начинается с нуля).

В строке 5 снова вызывается метод displayScores(), который выводит следующие значения:

Как видим значение 2-го элемента изменилось с 5 на 1 в результате присвоения в строке 4. Почему это важно? Потому что на наших глазах данные, которые якобы хранятся в private-переменной и не должны бесконтрольно меняться извне, изменились, что нарушает идею инкапсуляции. Кстати, почему это происходит?

Давайте снова посмотрим на setScores():


public void setScores(int[] scores) {
    this.scores = scores;
}

После присваивания обе переменные ссылаются на один и тот же объект в памяти — на массив myScores. Таким образом, изменения, которые могут быть внесены в this.scores, либо в myScores, фактически вносятся в один и тот же объект.

Решением этой проблемы является создание копии исходного массива и ее присвоение this.scores.

Модифицированная версия сеттера будет такой:


public void setScores(int[] scores) {
    this.scores = new int[scores.length];
    System.arraycopy(scores, 0, this.scores, 0, scores.length);
}

В чем разница? В том, что переменная this.scores больше не ссылается на тот же объект, на который ссылается scores. Вместо этого, массив this.scores инициализируется новым массивом с размером, равным размеру массива scores. Затем мы копируем все элементы из массива scores в this.scores, используя метод System.arraycopy().

Снова запускаем наш пример и получаем такой вывод:

Теперь два вызова метода displayScores() выводят один и тот же результат. Это означает, что массив this.scores независим и отличается от scores, переданного в сеттер. Таким образом, присваивание:

не влияет на массив this.scores.

Итак, правило такое: если вы передаете ссылку на объект в сеттер, то не копируйте ее во внутреннюю переменную напрямую. Вместо этого делайте ее копию и только тогда присваивайте ее полю.

Все то же самое можно сделать, используя метод copyOf или copyOfRange:


public void setScores(int[] scores) {
    this.scores = Arrays.copyOf(scores, scores.length);
}

Прочитать о разнице между arraycopy и copyOf можно в этой статье.

Ошибка № 3: Возврат геттером ссылки на объект.

Рассмотрим следующий геттер:


private int[] scores;
 
public int[] getScores() {
    return scores;
}

И следующий фрагмент кода:


int[] myScores = {5, 5, 4, 3, 2, 4}; // 1
 
setScores(myScores) // 2
displayScores();  // 3
 
int[] copyScores = getScores(); // 4
copyScores[1] = 1; // 5
displayScores(); // 6

Тогда мы получим следующий вывод:

Как вы заметили, 2-й элемент массива scores изменяется вне сеттера (в строке 5). Поскольку геттер возвращает ссылку на scores, внешний код, имея эту ссылку, может вносить изменения в массив.

Решение этой проблемы заключается в том, что геттеру необходимо возвращать копию объекта, а не ссылку на оригинал. Модифицируем вышеупомянутый геттер следующим образом:


public int[] getScores() {
    int[] copyScores = new int[scores.length];
    System.arraycopy(scores, 0, copyScores, 0, copyScores.length);
    return copyScores;
}

public int[] getScores() {
    return Arrays.copyOf(scores, scores.length);
}

Поэтому правило таково: не возвращайте ссылку на исходный объект в геттере. Возвращайте копию.

5. Реализация геттеров и сеттеров для примитивных типов

Переменные примитивных типов вы можете свободно передавать/возвращать прямо в сеттере/геттере, потому что Java автоматически копирует их значения. Таким образом, ошибок № 2 и № 3 можно избежать.

Например, следующий код безопасен, потому что сеттер и геттер работают с примитивным типом float:


private float amount;
 
public void setAmount(float amount) {
    this.amount = amount;
}
 
public float getAmount() {
    return amount;
}

Таким образом, примитивные типы не наделены проблемами объектов.

6. Реализация геттеров и сеттеров для объектов системных классов

Геттеры и сеттеры для String

String — это immutable-тип. Это означает, что после создания объекта этого типа, его значение нельзя изменить. Любые изменения будут приводить к созданию нового объекта String. Таким образом, как и для примитивных типов, вы можете безопасно реализовать геттер и сеттер для переменной String:


private String address;
 
public void setAddress(String address) {
    this.address = address;
}
 
public String getAddress() {
    return address;
}

Геттеры и сеттеры для объектов типа Date

Т.к. объекты класса java.util.Date являются изменяемыми, то внешние классы не должны иметь доступ к их оригиналам. Данный класс реализует метод clone() из класса Object, который возвращает копию объекта, но использовать его для этих целей не стоит.

По этому поводу Джошуа Блох пишет следующее: «Поскольку Date не является окончательным классом, нет га­рантии, что метод clone() возвратит объект, класс которого именно java.util.Date: он может вернуть экземпляр ненадежного подкласса, созданного специально для нанесения ущерба. Такой подкласс может, например, записы­вать ссылку на каждый экземпляр в момент создания последнего в закрытый статический список, а затем предоставить злоумышленнику доступ к этому списку. В результате злоумышленник получит полный контроль над всеми эк­земплярами копий. Чтобы предотвратить атаки такого рода, не используйте метод clone() для создания копии параметра, тип которого позволяет нена­дежным сторонам создавать подклассы».

Что тогда делать? Все очень просто — создавать каждый раз новый экземпляр и работать с ним:


private Date birthDate;
 
public void setBirthDate(Date birthDate) {
    this.birthDate = new Date(birthDate.getTime());
}
 
public Date getBirthDate() {
    return new Date(birthDate.getTime();
}

Вы можете узнать об этом больше в книге Джошуа Блоха «Java. Эффективное программирование» в главе 8.2: «При необходимости создавайте защитные копии».

7. Реализация геттеров и сеттеров для коллекций

Как описано в ошибках № 2 и № 3, иметь такого вида сеттеры и геттеры — не самая лучшая идея:


private List<String> listTitles;
 
public void setListTitles(List<String> listTitles) {
    this.listTitles = listTitles;
}
 
public List<String> getListTitles() {
    return listTitles;
}

Рассмотрим следующую программу:


import java.util.*;
 
public class CollectionGetterSetter {
    private List<String> listTitles;
 
    public void setListTitles(List<String> listTitles) {
        this.listTitles = listTitles;
    }
 
    public List<String> getListTitles() {
        return listTitles;
    }
 
    public static void main(String[] args) {
        CollectionGetterSetter app = new CollectionGetterSetter();
        List<String> titles1 = new ArrayList();
        titles1.add("Name");
        titles1.add("Address");
        titles1.add("Email");
        titles1.add("Job");
 
        app.setListTitles(titles1);
        System.out.println("Titles 1: " + titles1); 
        titles1.set(2, "Habilitation");
 
        List<String> titles2 = app.getListTitles();
        System.out.println("Titles 2: " + titles2);
        titles2.set(0, "Full name");
 
        List<String> titles3 = app.getListTitles();
        System.out.println("Titles 3: " + titles3);
    }
}

Мы можем ожидать, что в консоли будут отображены три одинаковых результата. Однако при запуске программа выдает следующее:

Это означает, что коллекция может быть изменена из кода, находящегося за пределами геттера и сеттера.

Для коллекции из String’ов одним из решений является использование конструктора, который принимает другую коллекцию в качестве аргумента. Например, мы можем изменить код вышеупомянутого геттера и сеттера следующим образом:


public void setListTitles(List<String> listTitles) {
    this.listTitles = new ArrayList<>(listTitles);
}
 
public List<String> getListTitles() {
    return new ArrayList<>(listTitles);   
}

Повторно скомпилируем и запустим программу CollectionGetterSetter. Она выдаст желаемый результат:

Обратите внимание: описанный выше подход с конструктором работает только с коллекциями из String-элементов, но он не будет работать для других объектов коллекций.

Рассмотрим следующий пример для коллекции объектов Person:


import java.util.*; 
   
public class CollectionGetterSetterObject { 
    private List<Person> listPeople; 
   
    public void setListPeople(List<Person> listPeople) { 
        this.listPeople = new ArrayList<>(listPeople); 
    } 
   
    public List<Person> getListPeople() { 
        return new ArrayList<>(listPeople); 
    } 
   
    public static void main(String[] args) { 
        CollectionGetterSetterObject app = new CollectionGetterSetterObject(); 
   
        List<Person> list1 = new ArrayList<>(); 
        list1.add(new Person("Peter")); 
        list1.add(new Person("Alice")); 
        list1.add(new Person("Mary")); 
   
        app.setListPeople(list1); 
   
        System.out.println("List 1: " + list1); 
   
        list1.get(2).setName("Maryland"); 
   
        List<Person> list2 = app.getListPeople(); 
        System.out.println("List 2: " + list2); 
   
        list1.get(0).setName("Peter Crouch"); 
   
        List<Person> list3 = app.getListPeople(); 
        System.out.println("List 3: " + list3); 
   
    } 
} 
   
class Person { 
    private String name; 
   
    public Person(String name) { 
        this.name = name; 
    }    
   
    public void setName(String name) { 
        this.name = name; 
    } 
   
    public String toString() { 
        return name; 
    } 
}

При запуске мы получим следующий вывод:

При создании новой коллекции на основе имеющейся, объекты, размещенные в оригинальной коллекции, не будут скопированы. Вместо этого в новую коллекцию будут скопированы только ссылки на эти объекты. И хоть в итоге две коллекции и различны, но содержат одни и те же объекты.

Также мы можем обнаружить, что ArrayList, HashMap, HashSet и т. д. реализуют свои собственные методы clone(). Эти методы якобы возвращают копии, но на самом деле копируют лишь ссылки (происходит неглубокое копирование). Об этом прямо написано в Javadoc метода clone() класса ArrayList:


Returns a shallow copy of this ArrayList instance. (The elements themselves are not copied)
public Object clone()

Что переводится, как «возвращает копию этого экземпляра ArrayList (сами элементы не копируются)».

Таким образом, мы не можем использовать метод clone() этих классов коллекций. Нам самостоятельно необходимо реализовать метод clone() в Person. Реализация будет выглядеть так:


public Object clone() {
    return new Person(name);
}

Сеттер для listPeople модицифируем следующим образом:


public void setListPeople(List<Person> listPeople) {
    for (Person person : listPeople) {
        this.listPeople.add((Person) person.clone());
    }
}

А геттер модифицируем так:


public List<Person> getListPeople() {
    List<Person> listPeople = new ArrayList<>();
    for (Person person : this.listPeople) {
        listPeople.add((Person) person.clone());
    }
    return listPeople;
}

Новая версия класса CollectionGetterSetterObject:


import java.util.*;
 
public class CollectionGetterSetterObject {
    private List<Person> listPeople = new ArrayList<>();
 
    public void setListPeople(List<Person> listPeople) {
        for (Person person : listPeople) {
            this.listPeople.add((Person) person.clone());
        }
    }
 
    public List<Person> getListPeople() {
        List<Person> listPeople = new ArrayList<>();
        for (Person person : this.listPeople) {
            listPeople.add((Person) person.clone());
        }
        return listPeople;
    }
 
    public static void main(String[] args) {
        CollectionGetterSetterObject app = new CollectionGetterSetterObject();
 
        List<Person> list1 = new ArrayList<>();
        list1.add(new Person("Peter"));
        list1.add(new Person("Alice"));
        list1.add(new Person("Mary"));
 
        app.setListPeople(list1);
 
        System.out.println("List 1: " + list1);
 
        list1.get(2).setName("Maryland");
 
        List<Person> list2 = app.getListPeople();
        System.out.println("List 2: " + list2);
 
        list1.get(0).setName("Peter Crouch");
 
        List<Person> list3 = app.getListPeople();
        System.out.println("List 3: " + list3);
 
    }
}

Запустив код выше, мы получим следующий вывод:

Таким образом, ключевыми моментами для реализации геттера и сеттера для коллекций являются:

  • Для коллекций из элементов типа String не требуется специальной реализации, так как объекты этого типа неизменяемы (immutable)
  • Для коллекций пользовательских типов данных необходимо:
    • реализовать метод clone()
    • в сеттере добавить клонирование элементов из исходной коллекции в конечную
    • в геттере создать новую возвращаемую коллекцию, используя клонирование элементов из исходной коллекции в новую

8. Реализация геттеров и сеттеров для вашего собственного класса

Если вы создаете объект своего пользовательского класса, вам следует для него реализовать метод clone().

Например:


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;
    }
 
    public String toString() {
        return name;
    }
 
    public Object clone() {
        return new Person(name);
    }
}

Как мы видим, класс Person реализует свой метод clone() для возврата клонированной версии самого себя.

При этом сеттер должен быть реализован следующим образом:


public void setFriend(Person friend) {
    this.friend = (Person) friend.clone();
}

public Person getFriend() {
    return (Person) friend.clone();
}

Итак, правила реализации геттера и сеттера для вашего собственного класса такие:

  • Реализуйте метод clone()
  • Возвращайте клонированный объект из геттера
  • Используйте клонированный объект в сеттере

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

Оцените статью, если она вам понравилась!

Предлагаем прочитать Вам 2 статьи о том, что такое геттеры и сеттеры. Это одна из статей из «Самоучителя по Java»  от Vertex Academy.

  • Статью №1 Вы найдете ниже.
  • Статью №2 Вы можете прочитать по ссылочке.
Что такое геттеры и сеттеры?

Следующее понятие из мира ООП, которое следует рассмотреть — это геттеры и сеттеры (getter — от англ. «get» — получать, и setter — от англ. «set» — устанавливать). Это общепринятый способ вводить данные («set») или получать данные («get»). Например, у меня есть класс Cat — кошка. Я задаю (используя setter) имя, пол, цвет глаз и окрас кошки (или кота):

А потом, если нужно узнать, какое у кошки имя — получаю с помощь getter — в данном случае getCatName :

Зачем они нужны?

Действительно — если я сам все задаю, зачем все эти геттеры и сеттеры?

Тут, нам стоит вспомнить про такой принцип ООП как инкапсуляция (если Вы не знаете, что это такое, вернитесь назад и прочитайте статью «Что такое ООП»). С помощью геттеров и сеттеров Вы защищаете содержимое программы — когда ей пользуется кто-то другой.

Представим, что создается программа, с помощью которой печатаются паспорта. Вам ведь не захочется, чтобы кто-то без доступа  вносил изменения в эту программу?

Как это работает?

Итак, как уже стало понятно, геттеры и сеттеры выполняют важную миссию защиты данных Вашей программы. Давайте рассмотрим, как это происходит на практике, и представим, что у нас есть такой класс Cat:

class Cat{

String name;

String color;

}

Вот, в нашем классе есть только имя и цвет кошки/кота. Как создать геттеры и сеттеры для переменных этого класса?

Тут понадобится две составляющие:

1. Задать полям, которые мы хотим защитить, свойство private.

class Cat{

private String name;

private String color;

}

2. Написать метод геттер/ сеттер для каждого поля.

Для поля name:

class Cat{

private String name;

private String color;

public String getName(){

      return name;

}

public void setName(String a){

      name=a;

}

}

Как Вы могли заметить, метод getName (имя метода-геттера состоит из слова get + название переменной) — это метод, который возвращает переменную типа String (т.е. имя кошки), и при этом не требует никаких данных.

Метод setName (имя метода-сеттера тоже состоит из слова set + название переменной) ничего не возвращает (void) но при этом требует String — новое значение для переменной name.

Теперь, таким же образом, напишем геттер и сеттер для переменной color:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

class Cat{

private String name;

private String color;

public String getName(){

      return name;

}

public void setName(String a){

      name = a;

}

public String getColor(){

      return color;

}

public void setColor(String color){

      this.color = color;

}

}

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

public void setColor(String color){

      this.color = color;

}

Посмотрите на слово this. Дело в том, что в данном методе есть две переменные с одинаковым названием — переменная String color, которую мы объявили ранее для всего класса, и переменная внутри метода:

Это означает, что программа не знает — когда Вы пишете, например, «color =», который из них Вы имеете ввиду? Поэтому, для обозначения переменной, объявленной для всего класса, используется слово «this«:

Поздравляем — теперь Вы знаете, как создавать геттеры и сеттеры!

О чем нужно помнить?

Пожалуйста, всегда обращайте внимание на следующее:

1. Наименование полей (переменных, содержащихся в классе) всегда пишется с маленькой буквы (например, int number, String name, и т.д.).

2. Как уже говорилось, наименование геттеров и сеттеров — в формате «get» + имя переменной с большой буквы (например, getColor, getName).

3. Метод геттер не имеет параметров (т.е. в скобках ничего не пишется) и возвращает значение одной переменной (одного поля).

4. Метод сеттер всегда имеет модификатор void и только один параметр, для изменения значения одного поля.


Cтатья написана Vertex Academy. Также мы проводим курсы Java с нуля. Детальнее на сайте.

Improve Article

Save Article

  • Read
  • Discuss
  • Improve Article

    Save Article

    Getter and Setter are methods used to protect your data and make your code more secure. Getter returns the value (accessors), it returns the value of data type int, String, double, float, etc. For the program’s convenience, getter starts with the word “get” followed by the variable name.

    While Setter sets or updates the value (mutators). It sets the value for any variable used in a class’s programs. and starts with the word “set” followed by the variable name. Getter and Setter make the programmer convenient in setting and getting the value for a particular data type. In both getter and setter, the first letter of the variable should be capital.

    Example 1

    Java

    import java.io.*;

    class GetSet {

        private String name;

        public String getName() { return name; }

        public void setName(String N)

        {

            this.name = N;

        }

    }

    class GFG {

        public static void main(String[] args)

        {

            GetSet obj = new GetSet();

            obj.setName("Geeks for Geeks");

            System.out.println(obj.getName());

        }

    }

    Getter and Setter give you the convenience of entering the value of the variables of any data type by the requirement of the code. Getters and setters let you manage how crucial variables in your code are accessed and altered. It can be seen in the program discussed below as follows:

    Example 2

    Java

    import java.io.*;

    class GetSet {

        private int num;

        public void setNumber(int number)

        {

            if (number < 1 || number > 10) {

                throw new IllegalArgumentException();

            }

            num = number;

        }

        public int getNumber() { return num; }

    }

    class GFG {

        public static void main(String[] args)

        {

            GetSet obj = new GetSet();

            obj.setNumber(5);

            System.out.println(obj.getNumber());

        }

    }

    Output explanation:

    Here we can see that if we take a value greater than 10 then it shows an error, By using the setNumber() method, one can be sure the value of a number is always between 1 and 10. This is much better than updating the number variable directly.

    Note: This could be avoided by making the number a private variable and utilizing the setNumber method. Using a getter method, on the other hand, is the sole way to read a number’s value.

    Over the years, I’ve come to believe that the whole notion of getter/setter is usually a mistake. As contrary as it may sound, a public variable is normally the correct answer.

    The trick is that the public variable should be of the correct type. In the question you’ve specified that either we’ve written a setter that does some checking of the value being written, or else that we’re only writing a getter (so we have an effectively const object).

    I would say that both of those are basically saying something like: «X is an int. Only it’s not really an int—it’s really something sort of like an int, but with these extra restrictions…»

    And that brings us to the real point: if a careful look at X shows that it’s really a different type, then define the type that it really is, and then create it as a public member of that type. The bare bones of it might look something like this:

    template <class T>
    class checked {
        T value;
        std::function<T(T const &)> check;
    
    public:
        template <class checker>
        checked(checker check) 
            : check(check)
            , value(check(T())) 
        { }
    
        checked &operator=(T const &in) { value = check(in); return *this; }
    
        operator T() const { return value; }
    
        friend std::ostream &operator<<(std::ostream &os, checked const &c) {
            return os << c.value;
        }
    
        friend std::istream &operator>>(std::istream &is, checked &c) {
            try {
                T input;
                is >> input;
                c = input;
            }
            catch (...) {
                is.setstate(std::ios::failbit);
            }
            return is;
        }
    };
    

    This is generic, so the user can specify something function-like (e.g., a lambda) that assures the value is correct—it might pass the value through unchanged, or it might modify it (e.g., for a saturating type) or it might throw an exception—but if it doesn’t throw, what it returns must be a value acceptable for the type being specified.

    So, for example, to get an integer type that only allows values from 0 to 10, and saturates at 0 and 10 (i.e., any negative number becomes 0, and any number greater than 10 becomes 10, we might write code on this general order:

    checked<int> foo([](auto i) { return std::min(std::max(i, 0), 10); });
    

    Then we can do more or less the usual things with a foo, with the assurance that it will always be in the range 0..10:

    std::cout << "Please enter a number from 0 to 10: ";
    std::cin >> foo; // inputs will be clamped to range
    
    std::cout << "You might have entered: " << foo << "n";
    
    foo = foo - 20; // result will be clamped to range
    std::cout << "After subtracting 20: " << foo;
    

    With this, we can safely make the member public, because the type we’ve defined it to be is really the type we want it to be—the conditions we want to place on it are inherent in the type, not something tacked on after the fact (so to speak) by the getter/setter.

    Of course, that’s for the case where we want to restrict the values in some way. If we just want a type that’s effectively read-only, that’s much easier—just a template that defines a constructor and an operator T, but not an assignment operator that takes a T as its parameter.

    Of course, some cases of restricted input can be more complex. In some cases, you want something like a relationship between two things, so (for example) foo must be in the range 0..1000, and bar must be between 2x and 3x foo. There are two ways to handle things like that. One is to use the same template as above, but with the underlying type being a std::tuple<int, int>, and go from there. If your relationships are really complex, you may end up wanting to define a separate class entirely to define the objects in that complex relationship.

    Summary

    Define your member to be of the type you really want, and all the useful things the getter/setter could/would do get subsumed into the properties of that type.

    Синтаксис get связывает свойство объекта с функцией, которая будет вызываться при обращении к этому свойству.

    Интерактивный пример

    Синтаксис

    {get prop() { ... } }
    {get [expression]() { ... } }
    

    Параметры

    prop

    Имя свойства для привязывания к заданной функции.

    expression

    Начиная с ECMAScript 6, вы также можете использовать выражения для вычисляемого имени свойства для привязки к заданной функции.

    Описание

    Иногда желательно разрешить доступ к свойству, которое возвращает динамически вычисляемое значение, или вы можете захотеть отражать состояние внутренней переменной без необходимости использования явных вызовов методов. В JavaScript, это можно реализовать при помощи использования геттера.

    Невозможно сделать так, чтобы геттер был привязан к свойству и одновременно чтобы это свойство действительно содержало значение, хотя можно использовать геттер и сеттер в сочетании, чтобы создать тип псевдо-свойство.

    Учтите следующее при работе с синтаксисом get:

    • Он может иметь идентификатор, который является либо числом, либо строкой;
    • Он должен иметь ровно 0 параметров (смотрите Incompatible ES5 change: literal getter and setter functions must now have exactly zero or one arguments для доп. информации);
    • Он не должен появляться в объектном литерале вместе с другим get или через ввод данных для того же свойства ({ get x() { }, get x() { } } и { x: ..., get x() { } } запрещены).

    Геттер можно удалить при помощи оператора delete.

    Примеры

    Определение геттера на новом объекте в момент инициализации этого объекта

    Ниже создаётся псевдо-свойство latest для объекта obj, который выведет последний элемент массива в консоль лог.

    const obj = {
      log: ['example','test'],
      get latest() {
        if (this.log.length === 0) return undefined;
        return this.log[this.log.length - 1];
      }
    }
    console.log(obj.latest); // "test"
    

    Обратите внимание, что попытка присвоить значение latest не изменит его.

    Удаление геттера оператором delete

    Если вы хотите удалить геттер, используйте delete:

    Определение геттера на уже существующих объектах с помощью defineProperty

    Для добавления геттера к существующему объекту в любое время используйте Object.defineProperty().

    const o = {a: 0};
    
    Object.defineProperty(o, 'b', { get: function() { return this.a + 1; } });
    
    console.log(o.b) // Runs the getter, which yields a + 1 (which is 1)
    

    Использование вычисляемого именованного свойства

    Примечание: Вычисляемые свойства являются экспериментальной технологией, частью предложений спецификации ECMAScript 6, и массовой поддержки браузерами пока нет. Код ниже вызовет синтаксическую ошибку в неподдерживаемых средах.

    var expr = "foo";
    
    var obj = {
      get [expr]() { return "bar"; }
    };
    
    console.log(obj.foo); // "bar"
    

    Умные / самостоятельно перезаписывающиеся/ ленивые геттеры

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

    Дополнительная техника оптимизации заключается в том, чтобы лениться или откладывать вычисление значения свойства и кешировать его для дальнейшего доступа. Так поступают умные или запоминающие геттеры. Значение вычисляется в первый раз при вызове геттера и затем сохраняется в кеше так, что последующие обращения будут возвращать кешированные значения без его пересчёта. Это полезно в следующих ситуациях:

    • Если вычисление значения свойства дорого (занимает много оперативной памяти или процессорного времени, порождает рабочий поток, получает удалённый файл, и т. д.).
    • Если сейчас это значение не нужно. Оно будет использоваться позже, или в некоторых случаях оно не используется вообще.
    • Если оно используется, к нему будут обращаться несколько раз, и нет необходимости его пересчитывать, так как значение не будет изменено, или не должно пересчитываться.

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

    В следующем примере у объекта есть геттер как собственное свойство. При получении свойства, свойство удаляется из объекта и вновь добавляется, но в этот раз неявно, как свойство с данными. В итоге значение возвращается.

    get notifier() {
      delete this.notifier;
      return this.notifier = document.getElementById("bookmarked-notification-anchor");
    },
    

    Для Firefox смотрите также модуль XPCOMUtils.jsm , который определяет функцию defineLazyGetter().

    get и defineProperty

    Использование ключевого слова get и Object.defineProperty() даёт похожие результаты, но при использовании в classes между ними есть тонкая разница.

    При использовании get свойство будет определено в прототипе объекта, в то время, как при использовании Object.defineProperty () свойство будет определено в экземпляре, к которому применяется.

    class Example {
      get hello() {
        return 'world';
      }
    }
    
    const obj = new Example();
    console.log(obj.hello);
    // "world"
    console.log(Object.getOwnPropertyDescriptor(obj, 'hello'));
    // undefined
    console.log(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(obj), 'hello'));
    // { configurable: true, enumerable: false, get: function get hello() { return 'world'; }, set: undefined }
    

    Спецификации

    Specification
    ECMAScript Language Specification
    # sec-method-definitions

    Совместимость с браузерами

    BCD tables only load in the browser

    Смотрите также

    Время на прочтение
    5 мин

    Количество просмотров 46K

    Javascript — очень изящный язык с кучей интересных возможностей. Большинство из этих возможностей скрыты одним неприятным фактором — Internet Explorer’ом и другим дерьмом, с которым нам приходится работать. Тем не менее, с приходом мобильных телефонов с актуальными браузерами и серверного JavaScript с нормальными движками эти возможности уже можно и нужно использовать прям сейчас. Но по привычке, даже при программировании для node.js мы стараемся писать так, чтобы оно работало в IE6+.

    В этой статье я расскажу про интересный и не секретный способ указывать изящные геттеры и сеттеры и немножко покопаемся в исходниках Mootools. Частично это информация взята из статьи John Resig, частично лично мой опыт и эксперименты.

    function Foo(bar){
        this._bar = bar;
    }
    
    Foo.prototype = {
        get bar () {
            return this._bar;
        },
        set bar (bar) {
            this._bar = bar;
        }
    };
    

    Стандартные геттеры

    Что такое Геттеры и Сеттеры, надеюсь знают все. Обычно и кроссбраузерно это выглядит так:

    function Foo(bar) {
    	this._bar = bar;
    };
    Foo.prototype = {
    	setBar : function (bar) {
    		this._bar = bar;
    	},
    	getBar : function () {
    		return this._bar;
    	}
    };
    var foo = new Foo;
    foo.setBar(123);
    alert(foo.getBar());
    

    Можно пойти дальше и написать более изящный вариант:

    function Foo(bar) {
    	var _bar = bar;
    
    	this.bar = function (bar) {
    		if (arguments.length)
    			_bar = bar;
    		else
    			return _bar;
    	}
    };
    var foo = new Foo;
    foo.bar(123);
    alert(foo.bar());
    

    Нативные геттеры/сеттеры

    Но есть более удобный способ, который работает во всех серверных движках и современных браузерах, а именно Firefox, Chrome, Safari3+, Opera9.5+ — задание сеттера и геттера для свойства так, чтобы продолжать обращатся к свойству, как свойству. У такого подхода есть несколько преимуществ:
    1. Более изящная запись. Представим ORM:

    for (var i in topics.comments);
    // vs
    for (var i in topics.loadComments());
    

    2. Если апи, которое базируется на свойствах уже есть и его нельзя менять (а очень нужно).

    Есть два способа задать такой геттер/сеттер:

    Через объект:

    function Foo(bar) {
    	this._bar = bar;
    };
    Foo.prototype = {
    	set bar (bar) {
    		this._bar = bar;
    	},
    	get bar () {
    		return this._bar;
    	}
    };
    var foo = new Foo;
    foo.bar = 123;
    alert(foo.bar);
    

    Через методы __defineGetter__ и __defineSetter__:

    function Foo(bar) {
    	var _bar = bar;
    
    	this.__defineGetter__("bar", function(){
    		return _bar;
    	});
    
    	this.__defineSetter__("bar", function(val){
    		_bar = bar;
    	});
    };
    var foo = new Foo;
    foo.bar = 123;
    alert(foo.bar);
    

    Определяем поддержку браузером

    Из этого можно получить лёгкий способ определения, поддерживает ли браузер геттеры или не поддерживает:

    return (typeof {}.__defineGetter__ == 'function');
    

    Как быть с наследованием?

    Получить саму функцию геттера или сеттера можно через методы .__lookupGetter__ и .__lookupSetter__.

    function extend(target, source) {
    	for ( var prop in source ) {
    		var getter = source.__lookupGetter__(prop),
    		    setter = source.__lookupSetter__(prop);
    
    		if ( getter || setter ) {
    			if ( getter ) target.__defineGetter__(prop, getter);
    			if ( setter ) target.__defineSetter__(prop, setter);
    		 } else
    			 a[i] = b[i];
    	}
    	return target;
    }
    

    Таким образом нашему target передадутся не значения родительского source, а функции-геттеры/сеттеры.

    Что следует помнить

    Некоторые замечания от John Resig:

    * Для каждого свойства вы можете установить только один геттер и/или сеттер. Не может быть два геттера или сеттера
    * Единственный способ удалить геттер или сеттер — это вызвать delete object[name];. Эта команда удаляет и геттер и сеттер, потому если вы хотите удалить что-то одно, а другое — оставить, надо сначала сохранить его, а после удаления — снова присвоить
    * Когда вы используете __defineGetter__ или __defineSetter__ он просто тихонько перезаписывает предыдущий геттер или сеттер и даже удаляет само свойство с таким именем.
    * Проверить, поддерживает ли ваш браузер геттеры и сеттеры можно с помощью простого сниппета:

    javascript:foo={get test(){ return "foo"; }};alert(foo.test);
    

    MooTools

    Мутулз не поддерживает по-умолчанию такую возможность. И, хотя я уже предложил патч, мы можем с лёгкостью (слегка изменив исходники) заставить его понимать геттеры и сеттеры.
    Итак, какая наша цель?

    var Foo = new Class({
    	set test : function () { console.log('test is set'); },
    	get test : function () { console.log('test is got'); return 'test'; },
    });
    foo.test = 1234; // test is set
    alert(foo.test); // test is get
    

    Более того, в классах унаследованных через Implements и Extends тоже должны работать геттеры и сеттеры родительского класса. Все наши действия будут происходить в файле [name: Class] внутри анонимной функции.
    Во-первых, внутри функции, в самом верху, определим функцию, которая перезаписывает только геттеры и сеттеры. И хотя мы отказалась от устаревших браузеров — стоит застраховаться.

    var implementGettersSetters = (typeof {}.__lookupGetter__ == 'function') ?
    	function (target, source) {
    		for (var key in source) {
    			var g = source.__lookupGetter__(key),
    			    s = source.__lookupSetter__(key);
    			if ( g || s ) {
    				if (g) target.__defineGetter__(key, g);
    				if (s) target.__defineSetter__(key, s);
    			}
    		}
    		return target;
    	} : function (target, source) { return target; };
    

    Конечно, если наш скрипт с такими геттерами попадёт в устаревший браузер, то он просто упадёт, но это страховка от того, чтобы кто-то случайно не взял этот файл и не прицепил его к себе на сайт, а потом недоумевал, что такое с ишаком.
    Мы видим, что если __lookupGetter__ не поддерживается, то функция просто ничего не сделает.

    Теперь заставляем работать getterы и setterы во время создания класса и наследования (Extends). Для этого:

    // после
    var Class = this.Class = new Type('Class', function(params){
    
    // сразу перед
    	newClass.$constructor = Class;
    	newClass.prototype.$constructor = newClass;
    	newClass.prototype.parent = parent;
    
    // Необходимо вставить функцию, которая расширит прототип (внимание, прототип, а не объект!) нашего класса:
    implementGettersSetters(newClass.prototype, params);
    

    Отдельным движением надо реализовать наследование геттеров и сеттеров от примесей (Implements). Для этого надо найти встроенные Мутаторы и добавить всего одну строку:

    Class.Mutators = {
    	// ...
    	Implements: function(items){
    		Array.from(items).each(function(item){
    			var instance = new item;
    			for (var key in instance) implement.call(this, key, instance[key], true);
    			// Вот она:
    			implementGettersSetters(this.prototype, instance);
    		}, this);
    	}
    };
    

    Все, теперь сеттеры и геттеры реализуются и мы с лёгкостью можем их наследовать и использовать. Наслаждайтесь)

    var Foo = new Class({
    	initialize : function (name) {
    		this.name = name;
    	},
    	set test : function () {
    		console.log(this.name + ' test is set');
    	},
    	get test : function () {
    		console.log(this.name + ' test is got');
    		return 'test';
    	},
    });
    var Bar = new Class({
    	Extends : Foo
    });
    var Qux = new Class({
    	Implements : [ Foo ]
    });
    var foo = new Foo('foo');
    foo.test = 1234; // foo test is set
    alert(foo.test); // foo test is got
    
    var bar = new Bar('bar');
    bar.test = 1234; // bar test is set
    alert(bar.test); // bar test is got
    
    var qux = new Qux('qux');
    qux.test = 1234; // qux test is set
    alert(qux.test); // qux test is got
    

    Интересные ссылки:

    Object.defineProperty — средство для создания свойств с очень широкими настройкам, такие как writable, get, set, configurable, enumerable
    Object.create — удобно быстро создавать нужные объекты.

    В этой статье вы узнаете о том, что такое getters и setters и зачем они нужны в JavaScript. 

    В прошлых статьях этого раздела мы говорили про объекта и их свойства. 

    У объектов в JavaScript два типа свойств.

    1. Свойства-данные — это обычные свойства, с которыми мы работали в предыдущих статьях. 
    2. Свойства-аксессоры — это функции дял присвоения и получения значений объекта. Вне объеккта они выглядят как обычные свойства.

    Свойства-аксессоры

    Свойства-аксессоры (accessor properties) — это 2 метода, которые получают или устанавливают значение объекта. Для этого используются два ключевых слова:

    • get — для геттеров (getters). Геттеры — для чтения, они позволяют получить значение объекта. 
    • set — для сеттеров (setters). Сеттеры — для записи, они позволяют присваиваивать значения объекта. 

    Геттеры

    Геттеры позволяют получить значение объекта. Рассмотрим на примере:

    const student = {
    
        // свойство-данные
        firstName: 'Маша',
        
        // свойство-аксессор (геттер)
        get getName() {
            return this.firstName;
        }
    };
    
    // доступ к свойству-данным
    console.log(student.firstName); // Вывод: Маша
    
    // доступ к свойству-аксессору
    console.log(student.getName); // Вывод: Маша
    
    // если попытаемся вызвать геттер как метод — получим ошибку
    console.log(student.getName()); // здесь будет ошибка

    В этом примере создан геттер getName(). Он позволяет получить доступ к свойству-данным firstName.

    get getName() {
        return this.firstName;
    }

    Примечание. Обратите внимание, что при объявлении геттера используется ключевоео слово get. Оно обязательно.

    Сеттеры

    Сеттеры позволяют изменять значение объекта. Рассмотрим на примере:

    const student = {
        firstName: 'Маша',
        
        // свойство-аксессор (сеттер)
        set changeName(newName) {
            this.firstName = newName;
        }
    };
    
    console.log(student.firstName); // Вывод: Маша
    
    // изменяем свойство объекта с помощью сеттера
    student.changeName = 'Настя';
    
    console.log(student.firstName); // Вывод: Настя

    В этом примере создан сеттер changeName(). Он позволяет получить доступ к свойству-данным firstName.

    set changeName(newName) {
        this.firstName = newName;
    }

    Примечание. Обратите внимание, что при объявлении сеттера используется ключевоео слово set. Оно обязательно.

    Сначала свойство firstName у объекта student'Маша'. С помощью сеттера мы меняем его на 'Настя'.

    Примечание. У сеттера обязательно должен быть только один формальный аргумент. Один сеттер изменяет одно свойство. 

    Object.defineProperty()

    Добавлять геттеры и сеттеры в объект можно еще и с помощью метода Object.defineProperty().

    Синтаксис

    Object.defineProperty(obj, prop, descriptor)
    • Первый аргумент obj — имя объекта.
    • Второй аргумент prop — имя свойства.
    • Третий аргумент descriptor — объект, описывающий свойство.

    Пример

    const student = {
        firstName: 'Маша'
    }
    
    // создаем геттер getName
    Object.defineProperty(student, "getName", {
        get : function () {
            return this.firstName;
        }
    });
    
    // создаем сеттер changeName
    Object.defineProperty(student, "changeName", {
        set : function (value) {
            this.firstName = value;
        }
    });
    
    console.log(student.firstName); // Вывод: Маша
    
    // изменяем свойство firstName 
    student.changeName = 'Настя';
    
    console.log(student.firstName); // Вывод: Настя

    Понравилась статья? Поделить с друзьями:
  • Как написать гет запрос
  • Как написать героя для книги
  • Как написать герману стерлигову
  • Как написать германия на немецком
  • Как написать географическое положение