Урок 17: Сеттеры и геттеры в Kotlin (setter, getter)

и Геттеры field как их кастомизировать 1 - Android [Kotlin] для начинающих

Общие понятия

Из прошлого урока про модификаторы доступа плавно переходим к пониманию сеттеров и геттеров. Когда мы обращаемся к свойству какого-то класса, можно подумать, что мы делаем это напрямую. Однако, это не так. Kotlin под капотом генерирует так называемые сеттеры и геттеры. Это методы с помощью которых мы можем получать и изменять свойства. По аналогии с теми фукнциями, что мы сами писали на прошлом уроке, только на уровне языка. Напомню, мы создали getNumberOfPages() и setNumberOfPages(), чтобы получать и задавать значение количества страниц.

Воспроизведем для начала такую простую ситуацию – чтение и запись свойства класса. Создадим Вавилонскую рыбку, изменяемое поле будет хранить уровень нервного сигнала. Рыбка, напомню, занимается переводом языков используя сигналы мозга. В рабочем файле создаем экземпляр, считываем и обновляем значение. Все это распечатываем в консоль для наглядности.

class BabelFish {

   var nerveSignalLevel: Int = 200

}
fun main() {

		val fish = BabelFish()

		println("old value: ${fish.nerveSignalLevel}")
        fish.nerveSignalLevel = 400
		println("new value: ${fish.nerveSignalLevel}")

}

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

Геттер (getter)

Мы можем написать их явно. Для этого переходим в созданный класс и под свойством nerveSignalLevel сначала пропишем геттер. Это просто функция get(). После нее через равно пишем то значение, которое она будет возвращать при чтении этой переменной у экземпляра класса.

Реализация по умолчанию

Мы можем отдавать то значение, которым проинициализировали переменную по умолчанию. Делается это с помощью ключевого слова field. Оно передает то поле, для которого мы явно прописываем геттер. Если навести на слово курсор, увидим полное название нашей переменной. А если кликнуть, среда разработки подсветит относящуюся к нему переменную. Это реализация по умолчанию, которую прописывать явно не имеет смысла. Именно поэтому среда разработки подсвечивает геттер волнистой линией и предлагает удалить его как избыточный.

var nerveSignalLevel: Int = 200
   get() = field

Произвольное значение

Другая ситуация, когда можно указать произвольное значение. Тогда оно и только оно будет возращаться при обращении к полю класса. Например, у нас переменная хранит 200, если тут проставим 250, запустив программу, увидим в консоли это значение.

var nerveSignalLevel: Int = 200
   get() = 250

Сеттер (setter)

Реализация для нового значения

Окей. Разобрали геттер, теперь про сеттер. Эта функция вызывается при записи в переменную класса нового значения. Выглядит она так:

var nerveSignalLevel: Int = 200
   get() = 250
   set(value: Int) {
			 field = value
   }

value в скобках – это объявленный параметр обычной функции, в него будет поступать значение. Название в принципе может быть любым. Тип автоматически будет таким же, как у значения переменной по умолчанию. Указывать его не обязательно, но для наглядности выведем его принудительно с помощью контекстного меню.

Ключевое слово field уже известно, его можно считать полем самого класса, для которого пишем сеттер. И ему присвамиваем поступающее в функцию значение из value. Мы написали реализацию сеттера по умолчанию. Для порядка проверим, передав какое-то значение в объект. Но сначала нужно откатить геттер до стандартной реализации, иначе он всегда будет возвращать 250.

class BabelFish {

   var nerveSignalLevel: Int = 200
      get() = field
	  set(value: Int) {
		  field = value
      }
}

Кастомизация геттера/сеттера

Хорошо. Для кастомизации вместо value можно поставить какое-либо значение. Но предлагаю воспроизвести более осмысленную ситуацию. Пропишем дополнительную логику. Например, добавим Boolean переменную isTranslated со значением по умолчанию false. Ее смысл в том, что при достаточном уровне нервного сигнала Вавилонская рыбка справляется с переводом и переменная принимает значение true. И эту проверку можно добавить в сеттер.

class BabelFish {

   var isTranslated: Boolean = false

   var nerveSignalLevel: Int = 200
      get() = field
	  set(value: Int) {
			field = value
	        if (value > 300) {
	            isTranslated = true
				println("isTranslated = true")
         }
      }
}

Теперь, если задать значение нервного сигнала больше 300, переменной isTranslated автоматически присвоится значение true. Посмотри как это выглядит.

fun main() {

    val fish = BabelFish()

		println("old value: ${fish.nerveSignalLevel}")
		println("old value: ${fish.isTranslated}")
        fish.nerveSignalLevel = 400
		println("new value: ${fish.nerveSignalLevel}")
		println("old value: ${fish.isTranslated}")

}

Итак, какие видим логи в консоли. Изначальное значение сигнала 200, isTranslated false. Присваиваем новое значение сигнала 400. Сеттер отрабатывает с методом распечатки isTranslated = true и проверяем поля заново. Сигнал 400 и isTranslated true.

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

Добавим целочисленное свойство с коэффициентом изменения сигнала. Так как он может отсутствовать вовсе, сделаем свойство нуллабельным.

В логику геттера добавим сначала проверку на null. Если коэффициент не null, возвращаем поле с уровнем заданного сигнала помноженное на коэффициент. Иначе просто возвращаем field.

class BabelFish(
   private val coefficient: Int?
) {

   var isTranslated: Boolean = false

   var nerveSignalLevel: Int = 200
      get() = if (coefficient != null) field * coefficient else field
	  set(value: Int) {
		  field = value
          if (value > 300) {
            isTranslated = true
			rintln("isTranslated = true")
         }
      }
}

Убедимся, что все работает. Создадим еще два объекта с разными коэффициентами. Теперь в консоль должен вывестись сначала уровень сигнала по умолчанию, затем с коэффициентом 2 и 21. Геттер отрабатывает корректно.

fun main() {

    val fish = BabelFish(null)

		println("old value: ${fish.nerveSignalLevel}")
		// println("old value: ${fish.isTranslated}")
		// fish.nerveSignalLevel = 400
		// println("new value: ${fish.nerveSignalLevel}")
		// println("old value: ${fish.isTranslated}")

    val fish2 = BabelFish(2)
		println("old value: ${fish2.nerveSignalLevel}")

    val fish3 = BabelFish(21)
		println("old value: ${fish3.nerveSignalLevel}")

}

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

Для тех, кто собрался стать Android-разработчиком

Пошаговаясхема
Пошаговая
схема

Описание процесса обучения от основ Kotlin до Android-разработчика

Бесплатныеуроки
Бесплатные
уроки

Авторский бесплатный курс по основам языка программирования Kotlin

Обучающийбот
Обучающий
бот

Тренажер и самоучитель по Котлин – бесплатные тесты и практика

Поделиться уроком

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *