Классы

Классы


В главное меню


Свойства класса

Функции класса описывают его поведение. Определения данных, чаще называемые свойствами класса, — это атрибуты, необходимые для выражения определенного состояния или характеристик класса. Например, свойства класса Player могут представлять имя игрока, состояние здоровья, расу, мировоззрение, пол и прочее.

Пример1: определение свойства класса
 
class Player {
    val name = "Madrigal"
}

Добаваили свойство name в тело класса, включая его как актуальное значение для экземпляра Player.

Пример2: определение свойств конструктора
 
class Player(val name: String, val age: Int) {
   
}

Создание класса в Kotlin

class User1(val name: String)       //Объявляем первичный конструктор

class User2 {  //Можем объявить вторичный конструктор или несколько
    val name: String
    constructor(name: String = "Max") {
        this.name = name
    }
}

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


Методы свойств

Для каждого объявленного свойства Kotlin сгенерирует поле, метод чтения (геттер) и, если надо, метод записи (сеттер).

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

class Player {
var name = "madrigal"
get() = field.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
        
set(value) {
    field = value.trim()
}

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

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

Примеры:

class Rectangle (private val height: Int, private val width: Int){
    val isSquare: Boolean
    get() = height == width

    val area: Int
    get() = height*width
}

fun main(){
    println(Rectangle(12,13).isSquare)
    println(Rectangle(12,12).area)
}

Видимость свойств

А что если вы решите открыть доступ к свойству для чтения и закрыть для записи?

private set(value) {
    field = value.trim()
}

Теперь name будет доступно для чтения из любой части NyetHack, но изменить его сможет только сам экземпляр Player. Эта техника весьма полезна, когда требуется запретить изменение свойства другими частями вашего приложения/

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

Это ограничение видимости некоторых свойств и/или функций класса. В то время как функцию класса public можно вызвать из любого места программы, функция класса private доступна только в классе, в котором объявлена. Все, что не задействуется, включая детали реализации функций и свойств, должно быть недоступно.


Init. Блок инициализации

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

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

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

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


Порядок инициализации

1.Создаются и инициализируются свойства, встроенные в объявление главного конструктора (val health: Int).

2.Выполняются операции присваивания на уровне класса (val race = "DWARF", val town = "Bavaria", val name = _name).

3.Выполняется блок init, присваивающий значения свойствам и вызывающий функции (alignment = "GOOD", вызов println).

4.Выполняется тело вспомогательного конструктора, в котором присваиваются значения свойствам и вызываются функции (town = "The Shire").

Поздняя инициализация

Класс Activity в Android представляет экран вашего приложения. Вы не контролируете момент, когда именно будет вызван конструктор Activity. Зато известно, что самая ранняя точка выполнения — это функция с именем onCreate. Если нельзя инициализировать свойства во время создания экземпляра, то когда это можно сделать?

Это та ситуация, когда важное значение приобретает поздняя инициализация, и это больше, чем просто нарушение правил инициализации компилятора Kotlin.

В любое объявление var-свойства можно добавить ключевое слово lateinit. Тогда компилятор Kotlin позволит отложить инициализацию свойства до того момента, когда такая возможность появится.

class Player {
    lateinit var alignment: String
    fun determineFate() {
        alignment = "Good"
    }
    fun proclaimFate() {
        if (::alignment.isInitialized) println(alignment)
    }
}

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

Ключевое слово lateinit действует как негласный договор: «Я обязуюсь инициализировать эту переменную до первой попытки обратиться к ней». Kotlin предоставляет инструмент для проверки факта инициализации таких переменных: метод isInitialized, показанный в примере выше. Вы можете вызывать isInitialized каждый раз, когда есть сомнения, что переменная lateinit инициализирована, чтобы избежать UninitializedPropertyAccessException.

Тем не менее isInitialized следует использовать экономно — например, не следует добавлять эту проверку к каждой переменной с поздней инициализацией. Если вы используете isInitialized слишком часто, это, скорее всего, означает, что лучше использовать тип с поддержкой null.


Синглтон

Существует в единственном экземпляре. Суть:

Конструктор приватный. Сначала проверяется существование синглтона.

Если не существует объект, то создаётся. Если существует, то возвращается существующий объект. Т.е. по сути создаётся один лишь раз. Далее мы можем его только вызывать. Создать новый нельзя, т.к. конструктор приватный.

  • Существует всегда один экземпляр класса
  • Новый создать невозможно

В Котлине

Немного иначе.

Создание синглтона:

object Singleton {
    var name: String = "Dima"
}

Потокобезопасный


Report Page