Шаг 138.
Основы Kotlin.
Объявление классов. Методы свойств

    На этом шаге мы рассмотрим определение и использование таких методов.

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

    Для каждого объявленного свойства Kotlin сгенерирует поле, метод чтения (геттер) и, если надо, метод записи (сеттер). Поле - это то место, где хранятся данные для свойства. Прямо объявить поле в классе нельзя. Kotlin инкапсулирует поля, защищая данные в поле и открывая доступ к ним через методы свойств. Метод чтения свойства определяет правила его чтения. Методы чтения создаются для всех свойств. Метод записи определяет правила присваивания значения свойству, поэтому он генерируется только для изменяемых свойств, - другими словами, если свойство объявлено с ключевым словом var.

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

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

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

    Чтобы увидеть, как переопределить метод чтения, добавьте метод get() в определение свойства name, который будет следить за тем, чтобы при обращении к этому свойству возвращалась строка, начинающаяся с прописной буквы.

class Player {
    val name = "madrigal"
        get() = field.capitalize()

    fun castFireball(numFireballs: Int = 2) =
            println("A glass of Fireball springs into existence. (x$numFireballs)")
}


Рис.1. Методы чтения (Player.kt)

    Объявляя свой метод чтения для свойства, вы меняете его поведение при попытке прочитать значение. Так как name содержит имя собственное, то при обращении к нему должна возвращаться строка, начинающаяся с прописной буквы. Наш метод чтения обеспечивает это.

    Запустите Game.kt и убедитесь, что Madrigal пишется с прописной M.


Рис.2. Результат работы приложения

    Ключевое слово field в примере ссылается на поле со значением, которое Kotlin автоматически создает для свойства. Поле - это место, откуда методы свойства читают и куда записывают данные, представляющие свойство. Это как ингредиенты на кухне ресторана - вызывающий никогда не видит исходные данные (ингредиенты), только то, что вернет ему метод чтения. Более того, доступ к полю можно получить только внутри методов этого свойства.

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

    Метод записи, напротив, изменяет поле свойства. Добавьте метод записи в определение свойства name и используйте в нем функцию trim(), чтобы убрать начальные и конечные пробелы из передаваемого значения.

class Player {
    val name = "madrigal"
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }

    fun castFireball(numFireballs: Int = 2) =
            println("A glass of Fireball springs into existence. (x$numFireballs)")
}


Рис.3. Объявление пользовательского сеттера (Player.kt)

    Добавление метода записи в это свойство породило проблему, о которой предупредит IntelliJ (рисунок 4).


Рис.4. Свойства val доступны только для чтения

    Свойство name объявлено как val, поэтому оно доступно только для чтения и не может быть изменено даже методом записи. Это защищает значения val от изменений без вашего согласия.

    В своей подсказке в IntelliJ сообщает важные сведения о методах записи: они вызываются для присваивания значений свойствам. Это не логично (а на самом деле это ошибка) - объявлять метод записи для свойства val, потому как если значение доступно только для чтения, то метод записи не сможет выполнить свою работу.

    Чтобы иметь возможность менять имя игрока, нужно заменить val на var в объявлении свойства name.

class Player {
    var name = "madrigal"
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }

    fun castFireball(numFireballs: Int = 2) =
            println("A glass of Fireball springs into existence. (x$numFireballs)")
}
Файл с проектом можно взять здесь.


Рис.5. Делаем имя изменяемым (Player.kt)

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

    Методы чтения свойств вызываются с помощью того же синтаксиса, что и другие переменные, с которыми вы уже знакомы. Методы записи в свойства вызываются автоматически, при попытке присвоить новое значение свойству с помощью оператора присваивания. Попытайтесь поменять имя игрока вне класса Player в Kotlin REPL.

val player = Player()
 player.name = "estragon "
 print(player.name + "TheBrave")

EstragonTheBrave


Рис.6. Изменяем имя игрока (REPL)

    Присваивание новых значений свойствам меняет состояние класса, которому они принадлежат. Если бы name все еще было объявлено как val, тогда пример, который вы ввели в REPL, выдал бы следующее сообщение об ошибке:

  error: val cannot be reassigned

    На следующем шаге мы рассмотрим видимость свойств.




Предыдущий шаг Содержание Следующий шаг