Php магические методы get. Создание методов get и set. Набор данных HashSet

Последнее обновление: 29.07.2018

Кроме обычных методов в языке C# предусмотрены специальные методы доступа, которые называют свойства . Они обеспечивают простой доступ к полям класса, узнать их значение или выполнить их установку.

Стандартное описание свойства имеет следующий синтаксис:

[модификатор_доступа] возвращаемый_тип произвольное_название { // код свойства }

Например:

Class Person { private string name; public string Name { get { return name; } set { name = value; } } }

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

Через это свойство мы можем управлять доступом к переменной name . Стандартное определение свойства содержит блоки get и set . В блоке get мы возвращаем значение поля, а в блоке set устанавливаем. Параметр value представляет передаваемое значение.

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

Person p = new Person(); // Устанавливаем свойство - срабатывает блок Set // значение "Tom" и есть передаваемое в свойство value p.Name = "Tom"; // Получаем значение свойства и присваиваем его переменной - срабатывает блок Get string personName = p.Name;

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

Class Person { private int age; public int Age { set { if (value < 18) { Console.WriteLine("Возраст должен быть больше 17"); } else { age = value; } } get { return age; } } }

Блоки set и get не обязательно одновременно должны присутствовать в свойстве. Если свойство определяют только блок get, то такое свойство доступно только для чтеня - мы можем получить его значение, но не установить. И, наоборот, если свойство имеет только блок set, тогда это свойство доступно только для записи - можно только установить значение, но нельзя получить:

Class Person { private string name; // свойство только для чтения public string Name { get { return name; } } private int age; // свойство только для записи public int Age { set { age = value; } } }

Модификаторы доступа

Мы можем применять модификаторы доступа не только ко всему свойству, но и к отдельным блокам - либо get, либо set:

Class Person { private string name; public string Name { get { return name; } private set { name = value; } } public Person(string name, int age) { Name = name; Age = age; } }

Теперь закрытый блок set мы сможем использовать только в данном классе - в его методах, свойствах, конструкторе, но никак не в другом классе:

Person p = new Person("Tom", 24); // Ошибка - set объявлен с модификатором private //p.Name = "John"; Console.WriteLine(p.Name);

При использовании модификаторов в свойствах следует учитывать ряд ограничений:

    Модификатор для блока set или get можно установить, если свойство имеет оба блока (и set, и get)

    Только один блок set или get может иметь модификатор доступа, но не оба сразу

    Модификатор доступа блока set или get должен быть более ограничивающим, чем модификатор доступа свойства. Например, если свойство имеет модификатор public, то блок set/get может иметь только модификаторы protected internal, internal, protected, private

Инкапсуляция

Выше мы посмотрели, что через свойства устанавливается доступ к приватным переменным класса. Подобное сокрытие состояния класса от вмешательства извне представляет механизм инкапсуляции , который представляет одну из ключевых концепций объектно-ориентированного программирования. (Стоит отметить, что само понятие инкапсуляции имеет довольно много различных трактовок, которые не всегда пересекаются друг с другом) Применение модификаторов доступа типа private защищает переменную от внешнего доступа. Для управления доступом во многих языках программирования используются специальные методы, геттеры и сеттеры. В C# их роль, как правило, выполняют свойства.

Например, есть некоторый класс Account, в котором определено поле sum, представляющее сумму:

Class Account { public int sum; }

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

Class Account { private int sum; public int Sum { get {return sum;} set { if (value > 0) { sum=value; } } } }

Автоматические свойства

Свойства управляют доступом к полям класса. Однако что, если у нас с десяток и более полей, то определять каждое поле и писать для него однотипное свойство было бы утомительно. Поэтому в фреймворк.NET были добавлены автоматические свойства. Они имеют сокращенное объявление:

Class Person { public string Name { get; set; } public int Age { get; set; } public Person(string name, int age) { Name = name; Age = age; } }

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

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

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

Автосвойствам можно присвоить значения по умолчанию (инициализация автосвойств):

Class Person { public string Name { get; set; } = "Tom"; public int Age { get; set; } = 23; } class Program { static void Main(string args) { Person person = new Person(); Console.WriteLine(person.Name); // Tom Console.WriteLine(person.Age); // 23 Console.Read(); } }

И если мы не укажем для объекта Person значения свойств Name и Age, то будут действовать значения по умолчанию.

Автосвойства также могут иметь модификаторы доступа:

Class Person { public string Name { private set; get;} public Person(string n) { Name = n; } }

Мы можем убрать блок set и сделать автосвойство доступным только для чтения. В этом случае для хранения значения этого свойства для него неявно будет создаваться поле с модификатором readonly, поэтому следует учитывать, что подобные get-свойства можно установить либо из конструктора класса, как в примере выше, либо при инициализации свойства:

Class Person { public string Name { get;} = "Tom" }

Сокращенная запись свойств

Как и методы, мы можем сокращать свойства. Например:

Class Person { private string name; // эквивалентно public string Name { get { return name; } } public string Name => name; }

Джозеф Кроуфорд, один из моих читателей, прочитал статью о том, как я не люблю писать getter’ы и setter’ы и предположил, что я могу использовать волшебные методы __get и __set.
Я скажу вам, почему это не очень хорошая идея, использовать их обычным способом. Кроме того, я собираюсь поведать вам историю, где они действительно оказались полезными, - речь пойдет о создании статических типов в PHP (динамический язык).
Для тех, кто не знаком с методами __get и __set - это два «магических» метода, которые работают следующим образом:
class Animal { function __get($property) { //... } function __set($property, $value) { //... } } $cow = new Animal; $cow->weight = "1 ton"; // same as $cow->__set("weight", "1 ton") print $cow->weight; // same as print $cow->__get("weight");

Как правило, вышеперечисленные методы используются для создания динамических свойств. Какой вывод можно из этого сделать? Если вы хотите создавать любые случайные свойства, просто используйте хэш (он же массив с ключами).
Что же хорошего в getter’ах и setter’ах?
Давайте посмотрим:
class Animal { public $weightInKgs; } $cow = new Animal; $cow->weightInKgs = -100;

Что? Вес с отрицательным значением? Это с большинства точек зрения неправильно.
Корова не должна весить меньше 100 кг (я так думаю:). В пределах 1000 - допустимо.
Как же нам обеспечить такое ограничение.
Использовать __get и __set - довольно быстрый способ.
class Animal { private $properties = array(); public function __get($name) { if(!empty($this->properties[$name])) { return $this->properties[$name]; } else { throw new Exception("Undefined property ".$name." referenced."); } } public function __set($name, $value) { if($name == "weight") { if($value < 100) { throw new Exception("The weight is too small!") } } $this->properties[$name] = $value; } } $cow = new Animal; $cow->weightInKgs = -100; // throws an Exception

А что если у вас есть класс с 10-20 свойствами и проверками для них? В этом случае неприятности неизбежны.
public function __set($name, $value) { if($name == "weight") { if($value < 100) { throw new Exception("The weight is too small!") } if($this->weight != $weight) { Shepherd::notifyOfWeightChange($cow, $weight); } } if($name == "legs") { if($value != 4) { throw new Exception("The number of legs is too little or too big") } $this->numberOfLegs = $numberOfLegs; $this->numberOfHooves = $numberOfLegs; } if($name == "milkType") { .... you get the idea .... } $this->properties[$name] = $value; }

И наоборот, getter’ы и setter’ы проявляют себя с лучшей стороны, когда дело доходит до проверки данных.
class Animal { private $weight; private $numberOfLegs; private $numberOfHooves; public $nickname; public function setNumberOfLegs($numberOfLegs) { if ($numberOfLegs != 100) { throw new Exception("The number of legs is too little or too big"); } $this->numberOfLegs = $numberOfLegs; $this->numberOfHooves = $numberOfLegs; } public function getNumberOfLegs() { return $this->numberOfLegs; } public function setWeight($weight) { if ($weight < 100) { throw new Exception("The weight is too small!"); } if($this->weight != $weight) { Shepherd::notifyOfWeightChange($cow, $weight); } $this->weight = $weight; } public function getWeight() { return $this->weight; } }

Ничто не идет в сравнение с краткими функциями {get, set;} из C#. Вполне вероятно, такая поддержка скоро появится в PHP, ну а пока не расслабляемся…
Каждый метод несет ответственность только за собственную область, благодаря этому в коде легче ориентироваться. Все равно получается слишком много кода, но он чище, чем __set-версия. Существует хороший эвристический подход, который заключается в следующем: если ваш метод (функция) занимает больше, чем 1 экран - нужно сокращать. Это улучшит восприятие кода.
Мы также храним некоторую бизнес-логику. Копыт всегда будет ровно столько, сколько и ног, а если мы заметим изменение веса скотинки, мы тут же уведомим пастуха.
Так как мы не заботимся о прозвищах коров и не проверяем их, пусть данные будут общедоступными без getter’ов и setter’ов.
Опять же, я действительно не писал всех этих getter’ов и setter’ов - PHP Storm сделал это за меня. Я просто написал следующее:
class Animal { private $weight; private $numberOfLegs; }

И нажал Alt+Insert -> Getters and setters. PHPStorm сгенерировал все автоматически.
Теперь в виде дополнительного преимущества PHP Storm при работе с getter’ами и setter’ами у меня есть возможность использовать функцию автозаполнения:

В случае с __get я не имею такой возможности, я могу лишь написать это:
$cow->wieght = -100

Теперь корова «весит» (wIEghts) минус 100 кг.
Я могу забыть, что это вес в кг, достаточно просто написать weight - все будет работать.
Итак, getter’ы and setter’ы бывают очень даже полезны (но все же не поклоняйтесь им, вы же не программист Java). Если вам просто нужны свободные свойства, используйте массив:
$cow = array("weight" => 100, "legs" => 4);

Этот трюк гораздо легче провернуть, чем __get и __set.
Но, если вы хотите быть уверенным, что ваши данные всегда имеют только допустимые значения, используйте setter’ы с проверкой. При наличии интегрированной среды разработки (IDE), типа PHP Storm, вы будете любить setter’ы, потому что они очень просты в использовании. Вместо $cow->setLegs() для PHP Storm достаточно будет набрать cosl. Да, легко! Нет больше опечаток, и вы можете видеть, какие параметры принимает метод.
Метод __set имеет и еще один недостаток. Он принимает только 1 параметр. Что делать, если вам нужно 2? Например, как здесь: $store1->setPrice("item-1", 100). Вам необходимо установить цену товара в магазине. Метод __set не позволит вам этого сделать, а setter позволит.

Чтобы контролировать использование полей, можно создать методы get и set и сделать их общедоступными. Они предоставляют возможность управлять доступом к полю. При этом, поле Age лучше сделать закрытым (private), чтобы к нему нельзя было получить прямой доступ за пределами класса.

public class Account

private int age;

public int GetAge()

return this.age;

public void SetAge(int inAge)

if ((inAge > 0) && (inAge < 120))

this.age = inAge;

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

Account s = new Account();

Console.WriteLine("Возраст: " + s.GetAge());

    1. Использование свойств

Свойства позволяют сделать управление данными более простым. Свойство Age можно объявить следующим образом:

public class Account

private int ageValue;

if ((value > 0) && (value < 120))

ageValue = value;

return ageValue;

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

Account s = new Account();

Console.WriteLine("Возраст: " + s.Age);

Когда свойству Age присваивается значение, вызывается код секции set. Ключевое слово value обозначает значение, которое присваивается свойству. При чтении значения свойства Age происходит вызов кода секции get. Такой подход сочетает преимущества использования методов и позволяет работать со свойствами так же просто, как и с полями класса.

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

Account s = new Account();

int newAge = 150;

if (s.Age != newAge)

Console.WriteLine("Значение возраста не было установлено");

Приведенный код пытается присвоить возрасту недопустимое значение 150, после чего проверяется, было ли это значение установлено. Если бы для присваивания значения использовался метод Set, он мог бы вернуть значение false в случае неудачи, а при использовании свойства пользователь должен выполнить чуть больше дополнительной работы.

Различные способы считывания значения свойства. Свойства позволяют выполнять и другие полезные действия.

public int AgeInMonths

return this.ageValue * 12;

Здесь описано новое свойство AgeInMonths. Оно предназначено только для чтения, так как не содержит секции set. Оно возвращает значение возраста в месяцах, используя то же самое значение, что и свойство Age. Это означает, что можно использовать несколько различных способов для получения одного и того же значения. Можно создать свойства только для чтения без возможности их изменения напрямую, а также свойства только для записи, хотя последние используются редко.

Свойства визуальных элементов . Свойства имеет смысл использовать в описании банковского счета, где нужно защитить данные в объектах. Но в программе Silverlight можно ввести любой текст в элемент TextBlock, и вроде бы нет нужды проверять допустимость вводимого значения. Выполнение этого кода замедлит процесс ввода значения. Так, сделав значение Text публичной строкой, программа содержала бы меньше кода и работала быстрее.

Но при этом, когда мы изменяем текст в элементе TextBlock, мы хотим, чтобы текст на странице Silverlight также изменился, например, когда программа Сумматор выведет на экран результат. Если бы программа просто изменила значение поля, система Silverlight никак не могла бы узнать, что сообщение на экране должно быть обновлено.

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

resultTextBlock.Text = "0";

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

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

Интерфейс Set включает следующие методы:

Метод Описание
add(Object o) Добавление элемента в коллекцию, если он отсутствует. Возвращает true, если элемент добавлен.
addAll(Collection c) Добавление элементов коллекции, если они отсутствуют.
clear() Очистка коллекции.
contains(Object o) Проверка присутствия элемента в наборе. Возвращает true, если элемент найден.
containsAll(Collection c) Проверка присутсвия коллекции в наборе. Возвращает true, если все элементы содержатся в наборе.
equals(Object o) Проверка на равенство.
hashCode() Получение hashCode набора.
isEmpty() Проверка наличия элементов. Возвращает true если в коллекции нет ни одного элемента.
iterator() Функция получения итератора коллекции.
remove(Object o) Удаление элемента из набора.
removeAll(Collection c) Удаление из набора всех элементов переданной коллекции.
retainAll(Collection c) Удаление элементов, не принадлежащих переданной коллекции.
size() Количество элементов коллекции
toArray() Преобразование набора в массив элементов.
toArray(T a) Преобразование набора в массив элементов. В отличии от предыдущего метода, который возвращает массив объектов типа Object, данный метод возвращает массив объектов типа, переданного в параметре.

К семейству интерфейса Set относятся HashSet , TreeSet и LinkedHashSet . В множествах Set разные реализации используют разный порядок хранения элементов. В HashSet порядок элементов оптимизирован для быстрого поиска. В контейнере TreeSet объекты хранятся отсортированными по возрастанию. LinkedHashSet хранит элементы в порядке добавления.

Набор данных HashSet

Конструкторы HashSet:

// Создание пустого набора с начальной емкостью (16) и со // значением коэффициента загрузки (0.75) по умолчанию public HashSet(); // Создание множества из элементов коллекции public HashSet(Collection c); // Создание множества с указанной начальной емкостью и со // значением коэффициента загрузки по умолчанию (0.75) public HashSet(int initialCapacity); // Создание множества с указанными начальной емкостью и // коэффициентом загрузки public HashSet(int initialCapacity, float loadFactor);

Методы HashSet

  • public int size()
  • public boolean isEmpty()
  • public boolean add(Object o)
  • public boolean addAll(Collection c)
  • public boolean remove(Object o)
  • public boolean removeAll(Collection c)
  • public boolean contains(Object o)
  • public void clear()
  • public Object clone()
  • public Iterator iterator()
  • public Object toArray()
  • public boolean retainAll(Collection c)

HashSet содержит методы аналогично ArrayList . Исключением является метод add(Object o), который добавляет объект только в том случае, если он отсутствует. Если объект добавлен, то метод add возвращает значение - true, в противном случае false.

Пример использования HashSet:

HashSet hashSet = new HashSet(); hashSet.add("Картофель"); hashSet.add("Морковь"); hashSet.add("Свекла"); hashSet.add("Огурцы"); // Следующая запись не должна попасть в набор hashSet.add("Картофель"); // Вывести в консоль размер набора System.out.println("Размер hashSet = " + hashSet.size()); // Вывести в консоль записи Iterator itr = hashSet.iterator(); while (itr.hasNext()) { System.out.println(itr.next().toString()); }

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

Пример использования HashSet с целочисленными значениями. В набор добавляем значения от 0 до 9 из 25 возможных случайным образом выбранных значений - дублирование не будет.

Random random = new Random(30); Set iset = new HashSet(); for(int i = 0; i < 25; i++) iset.add(random.nextInt(10)); // Вывести в консоль записи Iterator

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

Set set = Collections.synchronizedSet(new HashSet());

Набор данных LinkedHashSet

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

Конструкторы LinkedHashSet:

// Создание пустого набора с начальной емкостью (16) и со значением коэффициента загрузки (0.75) по умолчанию public LinkedHashSet() // Создание множества из элементов коллекции public LinkedHashSet(Collection c) // Создание множества с указанной начальной емкостью и со значением коэффициента загрузки по умолчанию (0.75) public LinkedHashSet(int initialCapacity) // Создание множества с указанными начальной емкостью и коэффициентом загрузки public LinkedHashSet(int initialCapacity, float loadFactor)

Также, как и HashSet , LinkedHashSet не синхронизируется. Поэтому при использовании данной реализации в приложении с множеством потоков, часть из которых может вносить изменения в набор, следует на этапе создания выполнить синхронизацию:

Set set = Collections.synchronizedSet(new LinkedHashSet());

Набор данных TreeSet

Класс TreeSet создаёт коллекцию, которая для хранения элементов использует дерево. Объекты хранятся в отсортированном порядке по возрастанию.

Конструкторы TreeSet:

// Создание пустого древовидного набора, с сортировкой согласно естественному // упорядочиванию его элементов TreeSet() // Создание древовидного набора, содержащего элементы в указанном наборе, // с сортировкой согласно естественному упорядочиванию его элементов. TreeSet(Collection c) // Создание пустого древовидного набора, с сортировкой согласно comparator TreeSet(Comparator comparator) // Создание древовидного набора, содержащего те же самые элементы и использующего // то же самое упорядочивание в качестве указанного сортированного набора TreeSet(SortedSet s)

Методы TreeSet