Kotlin data class setters

Свойства

Свойства в классах Kotlin могут быть объявлены либо как изменяемые (mutable) и неизменяемые (read-only) — var и val соответственно.

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

fun copyAddress(address: Address): Address < val result = Address() // в Kotlin нет никакого слова `new` result.name = address.name // вызов методов доступа result.street = address.street // . return result >

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

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

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

var initialized = 1 // имеет тип Int, стандартный геттер и сеттер // var allByDefault // ошибка: необходима явная инициализация, // предусмотрены стандартные геттер и сеттер 

Синтаксис объявления констант имеет два отличия от синтаксиса объявления изменяемых переменных: во-первых, объявление константы начинается с ключевого слова val вместо var , а во-вторых, объявление сеттера запрещено.

val simple: Int? // имеет тип Int, стандартный геттер, // должен быть инициализирован в конструкторе val inferredType = 1 // имеет тип Int и стандартный геттер 

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

class Rectangle(val width: Int, val height: Int) < val area: Int get() = this.width * this.height // тип свойства необязателен, поскольку он может быть выведен из возвращаемого типа геттера >

Вы можете опустить тип свойства, если его можно определить с помощью геттера.

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

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

var stringRepresentation: String get() = this.toString() set(value) < setDataFromString(value) // парсит строку и устанавливает // значения для других свойств >

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

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

var setterVisibility: String = "abc" private set // сеттер имеет private доступ и стандартную реализацию var setterWithAnnotation: Any? = null @Inject set // аннотирование сеттера с помощью Inject 

Теневые поля

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

var counter = 0 // инициализатор назначает резервное поле напрямую set(value) < if (value >= 0) field = value // значение при инициализации записывается // прямиком в backing field // counter = value // ERROR StackOverflow: Использование 'counter' сделало бы сеттер рекурсивным > 

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

Читайте также:  Php set defined variable

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

Например, в примере ниже не будет никакого теневого поля:

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

Теневые свойства

Если вы хотите предпринять что-то такое, что выходит за рамки вышеуказанной схемы неявного теневого поля, вы всегда можете использовать теневое свойство (backing property).

private var _table: Map? = null public val table: Map get() < if (_table == null) < _table = HashMap() // параметры типа вычисляются автоматически // (ориг.: "Type parameters are inferred") >return _table ?: throw AssertionError("Set to null by another thread") > 

On the JVM: Access to private properties with default getters and setters is optimized to avoid function call overhead. —>

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

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

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

  • Находиться на самом высоком уровне или быть членами объявления object или вспомогательного объекта;
  • Быть проинициализированными значением типа String или значением примитивного типа;
  • Не иметь переопределённого геттера.

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

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" @Deprecated(SUBSYSTEM_DEPRECATED) fun foo()

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

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

Для того чтобы справиться с такой задачей, вы можете пометить свойство модификатором lateinit .

public class MyTest < lateinit var subject: TestSubject @SetUp fun setup() < subject = TestSubject() >@Test fun test() < subject.method() // объект инициализирован, проверять на null не нужно >> 

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

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

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

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

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

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

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

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

Читайте также:  Require once wp config php

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

© 2015—2023 Open Source Community

Источник

Properties

Properties in Kotlin classes can be declared either as mutable, using the var keyword, or as read-only, using the val keyword.

To use a property, simply refer to it by its name:

Getters and setters

The full syntax for declaring a property is as follows:

The initializer, getter, and setter are optional. The property type is optional if it can be inferred from the initializer or the getter’s return type, as shown below:

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

The full syntax of a read-only property declaration differs from a mutable one in two ways: it starts with val instead of var and does not allow a setter:

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

You can define custom accessors for a property. If you define a custom getter, it will be called every time you access the property (this way you can implement a computed property). Here’s an example of a custom getter:

You can omit the property type if it can be inferred from the getter:

If you define a custom setter, it will be called every time you assign a value to the property, except its initialization. A custom setter looks like this:

By convention, the name of the setter parameter is value , but you can choose a different name if you prefer.

If you need to annotate an accessor or change its visibility, but you don’t want to change the default implementation, you can define the accessor without defining its body:

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

Backing fields

In Kotlin, a field is only used as a part of a property to hold its value in memory. Fields cannot be declared directly. However, when a property needs a backing field, Kotlin provides it automatically. This backing field can be referenced in the accessors using the field identifier:

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 >

The field identifier can only be used in the accessors of the property.

A backing field will be generated for a property if it uses the default implementation of at least one of the accessors, or if a custom accessor references it through the field identifier.

For example, there would be no backing field in the following case:

Backing properties

If you want to do something that does not fit into this implicit backing field scheme, you can always fall back to having a backing property:

private var _table: Map? = null public val table: Map get() < if (_table == null) < _table = HashMap() // Type parameters are inferred >return _table ?: throw AssertionError(«Set to null by another thread») >

Читайте также:  Неоновый розовый цвет html

On the JVM: Access to private properties with default getters and setters is optimized to avoid function call overhead.

Compile-time constants

If the value of a read-only property is known at compile time, mark it as a compile time constant using the const modifier. Such a property needs to fulfil the following requirements:

  • It must be a top-level property, or a member of an object declaration or a companion object.
  • It must be initialized with a value of type String or a primitive type
  • It cannot be a custom getter

The compiler will inline usages of the constant, replacing the reference to the constant with its actual value. However, the field will not be removed and therefore can be interacted with using reflection.

Such properties can also be used in annotations:

const val SUBSYSTEM_DEPRECATED: String = «This subsystem is deprecated» @Deprecated(SUBSYSTEM_DEPRECATED) fun foo() < . >

Late-initialized properties and variables

Normally, properties declared as having a non-null type must be initialized in the constructor. However, it is often the case that doing so is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In these cases, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.

To handle such cases, you can mark the property with the lateinit modifier:

This modifier can be used on var properties declared inside the body of a class (not in the primary constructor, and only when the property does not have a custom getter or setter), as well as for top-level properties and local variables. The type of the property or variable must be non-null, and it must not be a primitive type.

Accessing a lateinit property before it has been initialized throws a special exception that clearly identifies the property being accessed and the fact that it hasn’t been initialized.

Checking whether a lateinit var is initialized

To check whether a lateinit var has already been initialized, use .isInitialized on the reference to that property:

This check is only available for properties that are lexically accessible when declared in the same type, in one of the outer types, or at top level in the same file.

Overriding properties

Delegated properties

The most common kind of property simply reads from (and maybe writes to) a backing field, but custom getters and setters allow you to use properties so one can implement any sort of behavior of a property. Somewhere in between the simplicity of the first kind and variety of the second, there are common patterns for what properties can do. A few examples: lazy values, reading from a map by a given key, accessing a database, notifying a listener on access.

Such common behaviors can be implemented as libraries using delegated properties.

Источник

Оцените статью