Spec-Zone.ru › Kotlin 1.7

Свойства

Объявление свойств

Свойства в классах Kotlin могут быть объявлены как изменяемые, используя ключевое слово var, или как неизменяемые (только для чтения), используя ключевое слово val.

class Address {
    var name: String = "Holmes, Sherlock"
    var street: String = "Baker"
    var city: String = "London"
    var state: String? = null
    var zip: String = "123456"
}

Для использования свойства, просто обратитесь к нему по имени:

fun copyAddress(address: Address): Address {
    val result = Address() // there's no 'new' keyword in Kotlin
    result.name = address.name // accessors are called
    result.street = address.street
    // ...
    return result
}

Геттеры и сеттеры

Полный синтаксис объявления свойства:

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

Инициализатор, геттер и сеттер являются необязательными. Тип свойства необязателен, если его можно вывести из инициализатора или типа возвращаемого значения геттера, как показано ниже:

var initialized = 1 // has type Int, default getter and setter
// var allByDefault // ERROR: explicit initializer required, default getter and setter implied

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

val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter

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

//sampleStart
class Rectangle(val width: Int, val height: Int) {
    val area: Int // property type is optional since it can be inferred from the getter's return type
        get() = this.width * this.height
}
//sampleEnd
fun main() {
    val rectangle = Rectangle(3, 4)
    println("Width=${rectangle.width}, height=${rectangle.height}, area=${rectangle.area}")
}

Вы можете опустить тип свойства, если его можно вывести из геттера:

val area get() = this.width * this.height

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

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // parses the string and assigns values to other properties
    }

По соглашению, имя параметра сеттера — value, но вы можете выбрать другое имя, если хотите.

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

var setterVisibility: String = "abc"
    private set // the setter is private and has the default implementation

var setterWithAnnotation: Any? = null
    @Inject set // annotate the setter with Inject

Вспомогательные поля

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

var counter = 0 // the initializer assigns the backing field directly
    set(value) {
        if (value >= 0)
            field = value
            // counter = value // ERROR StackOverflow: Using actual name 'counter' would make setter recursive
    }

Идентификатор field может использоваться только в аксессорах свойства.

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

Например, в следующем случае вспомогательного поля не будет:

val isEmpty: Boolean
    get() = this.size == 0

Вспомогательные свойства

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

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // Type parameters are inferred
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

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

Константы времени компиляции

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

  • Оно должно быть свойством верхнего уровня или членом объявления object или компаньон-объекта.

  • Оно должно быть инициализировано значением типа String или примитивного типа

  • Это не может быть пользовательский геттер

Компилятор выполнит встраивание использования константы, заменив ссылку на константу ее фактическим значением. Однако поле не будет удалено и поэтому может быть использовано с помощью рефлексии.

Такие свойства также могут использоваться в аннотациях:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }

Свойства и переменные с отложенной инициализацией

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

Для таких случаев можно использовать модификатор lateinit.

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // dereference directly
    }
}

Этот модификатор может быть применен к var свойствам, объявленным внутри тела класса (не в главном конструкторе, и только когда у свойства нет пользовательского геттера или сеттера), а также для свойств и локальных переменных верхнего уровня. Тип свойства или переменной должен быть не-null и не должен быть примитивным типом.

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

Проверка инициализации lateinit var

Чтобы проверить, было ли свойство lateinit var уже инициализировано, используйте .isInitialized для ссылки на это свойство:

if (foo::bar.isInitialized) {
    println(foo.bar)
}

Эта проверка доступна только для свойств, лексически доступных при объявлении в том же типе, во внешних типах или на верхнем уровне в том же файле.

Переопределение свойств

См. Переопределение свойств

Делегированные свойства

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

Такое общее поведение может быть реализовано в библиотеках с использованием делегированных свойств.

Последнее изменение: 06 сентября 2022
Наследование Интерфейсы

© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/properties.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API