Шаг 156.
Основы Kotlin.
Инициализация. Инициализация свойств

    На этом шаге мы рассмотрим особенности инициализации свойств.

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

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

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


Рис.1. Объявление свойства hometown (Player.kt)

    Вы объявили hometown, но компилятору Kotlin этого мало. Объявления имени и типа свойства недостаточно, так как необходимо также присвоить начальное значение при объявлении свойства. Зачем? Дело в правилах защиты от null. Без начального значения свойство может быть null, что недопустимо, если свойство имеет тип, не поддерживающий null.

    Чтобы решить эту проблему, можно "приложить подорожник", то есть инициализировать hometown пустой строкой:


Рис.2. Инициализация hometown (Player.kt)

    Этот код скомпилируется, но это не идеальное решение, потому что пустая строка "" не является названием города в NyetHack. Для решения проблемы добавим новую функцию с именем selectHometown, которая возвращает случайный город из списка. Мы используем эту функцию, чтобы присвоить начальное значение hometown.

import java.io.File

class Player (_name: String,
              var healthPoints: Int = 100,
              val isBlessed: Boolean,
              private val isImmortal: Boolean) {
    var name = _name
        get() = field.capitalize()
        private set(value) {
            field = value.trim()
        }
    init {
        require(healthPoints > 0, { "healthPoints must be greater than zero." })
        require(name.isNotBlank(), { "Player must have a name." })
    }

    val hometown = selectHometown()

    constructor(name: String) : this(name,
            isBlessed = true,
            isImmortal = false) {
        if (name.toLowerCase() == "kar") healthPoints = 40
    }

    private fun selectHometown() = File("data/towns.txt")
            .readText()
            .split("\n")
            .shuffled()
            .first()
}


Рис.3. Объявление функции selectHometown() (Player.kt)


Не забудьте добавить import java.io.File в Player.kt, чтобы получить доступ к классу File.

    Вам придется добавить файл towns.txt со списком городов в папку data с данными.

    Испытайте свойство hometown, используя метод чтения свойства name. Чтобы отличать вашего героя от всех остальных Madrigals мира, герой должен упоминаться по имени со ссылкой на его родной город.

import java.io.File

class Player (_name: String,
              var healthPoints: Int = 100,
              val isBlessed: Boolean,
              private val isImmortal: Boolean) {
    var name = _name
        get() = "${field.capitalize()} of $hometown"
        private set(value) {
            field = value.trim()
        }
    init {
        require(healthPoints > 0, { "healthPoints must be greater than zero." })
        require(name.isNotBlank(), { "Player must have a name." })
    }

    val hometown = selectHometown()

    constructor(name: String) : this(name,
            isBlessed = true,
            isImmortal = false) {
        if (name.toLowerCase() == "kar") healthPoints = 40
    }

    private fun selectHometown() = File("data/towns.txt")
            .readText()
            .split("\n")
            .shuffled()
            .first()
}
Файл с проектом можно взять здесь.


Рис.4. Использование свойства hometown (Player.kt)

    Запустите Game.kt. Теперь всех ваших героев с одинаковыми именами можно различить по городу рождения:

A glass of Fireball springs into existence. (x2)
(Aura: GREEN) (Blessed: YES)
Madrigal of Sanorith is in excellent condition!


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


Отметим, что если у вас результат работы приложения отличается от приведенного на рисунке 5, то попробуйте использовать вместо символа-разделителя "\n" комбинацию "\r\n". Это вызвано особенностями обработки разделителей строк операционной системой.

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

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

  class JazzPlayer {
    fun acquireMusicalInstrument() { 
      val instrumentName: String instrumentName = "Oboe"
    }
  }

    Так как значение присваивается переменной instrumentName раньше обращений к ней, код благополучно компилируется.

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

    На следующем шаге мы рассмотрим порядок инициализации.




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