Свойства
В Kotlin свойства позволяют хранить данные и управлять ими, не создавая функции для доступа к данным или их изменения. Свойства можно использовать в классах, интерфейсах, объектах, объектах-компаньонах и даже вне этих структур — как свойства верхнего уровня.
У каждого свойства есть имя, тип и автоматически сгенерированная функция get(), которая называется геттером. Геттер позволяет получить значение свойства. Если свойство изменяемое, у него также есть функция set(), которая называется сеттером и позволяет изменить значение свойства.
Объявление свойств
Свойства могут быть изменяемыми (var) или доступными только для чтения (val). Их можно объявить как свойство верхнего уровня в файле .kt. Считайте свойство верхнего уровня глобальной переменной, принадлежащей пакету:
// File: Constants.kt package my.app val pi = 3.14159 var counter = 0
Также можно объявлять свойства внутри класса, интерфейса или объекта:
// Class with properties
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
}
// Interface with a property
interface ContactInfo {
val email: String
}
// Object with properties
object Company {
var name: String = "Detective Inc."
val country: String = "UK"
}
// Class implementing the interface
class PersonContact : ContactInfo {
override val email: String = "sherlock@example.com"
}
Чтобы использовать свойство, обратитесь к нему по имени:
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
}
interface ContactInfo {
val email: String
}
object Company {
var name: String = "Detective Inc."
val country: String = "UK"
}
class PersonContact : ContactInfo {
override val email: String = "sherlock@example.com"
}
//sampleStart
fun copyAddress(address: Address): Address {
val result = Address()
// Accesses properties in the result instance
result.name = address.name
result.street = address.street
result.city = address.city
return result
}
fun main() {
val sherlockAddress = Address()
val copy = copyAddress(sherlockAddress)
// Accesses properties in the copy instance
println("Copied address: ${copy.name}, ${copy.street}, ${copy.city}")
// Copied address: Holmes, Sherlock, Baker, London
// Accesses properties in the Company object
println("Company: ${Company.name} in ${Company.country}")
// Company: Detective Inc. in UK
val contact = PersonContact()
// Access properties in the contact instance
println("Email: ${contact.email}")
// Email: sherlock@email.com
}
//sampleEnd
В Kotlin рекомендуется инициализировать свойства при объявлении — так код будет безопаснее и понятнее. Однако в особых случаях их можно инициализировать позже.
Объявлять тип свойства необязательно, если компилятор может вывести его из инициализатора или типа возвращаемого значения геттера:
var initialized = 1 // The inferred type is Int var allByDefault // ERROR: Property must be initialized.
Пользовательские геттеры и сеттеры
По умолчанию Kotlin автоматически генерирует геттеры и сеттеры. Если нужна дополнительная логика, например проверка, форматирование или вычисления на основе других свойств, можно определить собственные аксессоры.
Пользовательский геттер выполняется при каждом обращении к свойству:
//sampleStart
class Rectangle(val width: Int, val height: Int) {
val area: Int
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
Пользовательский сеттер выполняется при каждом присваивании значения свойству, кроме случаев инициализации. По соглашению параметр сеттера называется value, но можно выбрать другое имя:
class Point(var x: Int, var y: Int) {
var coordinates: String
get() = "$x,$y"
set(value) {
val parts = value.split(",")
x = parts[0].toInt()
y = parts[1].toInt()
}
}
fun main() {
val location = Point(1, 2)
println(location.coordinates)
// 1,2
location.coordinates = "10,20"
println("${location.x}, ${location.y}")
// 10, 20
}
Изменение видимости или добавление аннотаций
В Kotlin можно изменить видимость аксессора или добавить аннотации, не заменяя реализацию по умолчанию. Для этого не нужно объявлять тело {}.
Чтобы изменить видимость аксессора, укажите модификатор перед ключевым словом get или set:
class BankAccount(initialBalance: Int) {
var balance: Int = initialBalance
// Only the class can modify the balance
private set
fun deposit(amount: Int) {
if (amount > 0) balance += amount
}
fun withdraw(amount: Int) {
if (amount > 0 && amount <= balance) balance -= amount
}
}
fun main() {
val account = BankAccount(100)
println("Initial balance: ${account.balance}")
// 100
account.deposit(50)
println("After deposit: ${account.balance}")
// 150
account.withdraw(70)
println("After withdrawal: ${account.balance}")
// 80
// account.balance = 1000
// Error: cannot assign because setter is private
}
Чтобы добавить аннотацию к аксессору, укажите её перед ключевым словом get или set:
// Defines an annotation that can be applied to a getter
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class Inject
class Service {
var dependency: String = "Default Service"
// Annotates the getter
@Inject get
}
fun main() {
val service = Service()
println(service.dependency)
// Default service
println(service::dependency.getter.annotations)
// [@Inject()]
println(service::dependency.setter.annotations)
// []
}
В этом примере с помощью рефлексии показано, какие аннотации присутствуют у геттера и сеттера.
Поля хранения
Компилятор автоматически создаёт поля хранения для свойств, когда значение нужно хранить в памяти.
Например, компилятор создаёт поле хранения при использовании функций get() и set() по умолчанию, поскольку они считывают и записывают сохранённое значение:
var count = 0
Доступ к полям хранения можно получить с помощью ключевого слова field в пользовательской функции get() или set(). Например, можно добавить дополнительную логику в геттер или сеттер либо выполнять дополнительное действие при изменении свойства.
В этом примере свойство score использует поле хранения внутри функции set(), чтобы при обновлении значения также записывалось событие в журнал:
class Scoreboard {
var score: Int = 0
set(value) {
field = value
// Adds logging when updating the value
println("Score updated to $field")
}
}
fun main() {
val board = Scoreboard()
board.score = 10
// Score updated to 10
board.score = 20
// Score updated to 20
}
Поля хранения создаются не для всех свойств, поскольку они могут быть не нужны. Например, у свойства isEmpty нет поля хранения, поскольку его значение вычисляется на основе свойства size при каждом обращении:
val isEmpty: Boolean
get() = this.size == 0
Явные поля хранения
Иногда может понадобиться большая гибкость. Например, если в API свойство должно изменяться внутри класса, но не извне. В таких случаях можно использовать явное поле хранения.
В следующем примере класс ShoppingCart содержит свойство items, представляющее всё содержимое корзины покупок. Класс предоставляет свойство items как доступный только для чтения список строк, но внутри хранит данные в изменяемом списке с явным полем хранения:
class ShoppingCart {
// Public read-only view with explicit backing field
val items: List<String>
field = mutableListOf()
fun addItem(item: String) {
items.add(item)
}
fun removeItem(item: String) {
items.remove(item)
}
}
fun main() {
val cart = ShoppingCart()
cart.addItem("Apple")
cart.addItem("Banana")
println(cart.items)
// [Apple, Banana]
cart.removeItem("Apple")
println(cart.items)
// [Banana]
}
В этом примере компилятор выводит тип поля хранения из вызова mutableListOf(): MutableList<String>. Тип поля хранения также можно объявить явно:
val items: List<String>
// Explicit backing field with explicit type
field: MutableList<String> = mutableListOf()
В примере с классом ShoppingCart компилятор выполняет умное приведение свойства items к типу MutableList<String>, поэтому класс может добавлять элементы в корзину и удалять их с помощью функций add() и remove(). За пределами класса компилятор использует публичный тип свойства List<String>, поэтому пользователи API могут только просматривать содержимое списка items.
Ограничения
Чтобы использовать явные поля хранения, свойства и сами поля хранения должны соответствовать определённым правилам. Явные поля хранения можно использовать только для свойств, которые:
Не имеют пользовательского геттера.
Доступны только для чтения (
val).Не являются
open.Не являются делегированными свойствами.
Не являются константами времени компиляции.
Кроме того, тип поля хранения должен быть подтипом типа свойства, а его видимость должна быть private.
Обойти эти ограничения можно с помощью свойств хранения.
Свойства хранения
Если явные поля хранения не подходят для вашей задачи, можно попробовать использовать шаблон написания кода под названием свойство хранения.
Например, если для свойства нужен пользовательский геттер:
class UserDirectory {
private val _users = mutableListOf(
"sarah",
"mike",
"emma"
)
val users: List<String>
get() = _users.sorted()
fun addUser(username: String) {
_users.add(username)
}
}
fun main() {
val directory = UserDirectory()
directory.addUser("alex")
println(directory.users)
// [alex, emma, mike, sarah]
}
В этом примере класс UserDirectory содержит доступное только для чтения свойство users со списком всех пользователей каталога. Переменная _users — это закрытое свойство хранения, содержащее исходный список. Геттер публичного свойства users сортирует элементы перед возвратом.
Константы времени компиляции
Если значение свойства, доступного только для чтения, известно во время компиляции, пометьте его модификатором const, чтобы объявить его константой времени компиляции. Такие константы встраиваются во время компиляции, поэтому каждая ссылка на них заменяется фактическим значением. Доступ к ним выполняется эффективнее, поскольку вызов геттера не требуется:
// File: AppConfig.kt package com.example // Compile-time constant const val MAX_LOGIN_ATTEMPTS = 3
Константы времени компиляции должны соответствовать следующим требованиям:
Они должны быть свойствами верхнего уровня либо членами объявления
objectили объекта-компаньона.Их значения должны иметь тип
Stringили примитивный тип.У них не может быть пользовательского геттера.
У констант времени компиляции по-прежнему есть поле хранения, поэтому с ними можно взаимодействовать с помощью рефлексии.
Эти свойства также можно использовать в аннотациях:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun processLegacyOrders() { ... }
Свойства и переменные с отложенной инициализацией
Обычно свойства необходимо инициализировать в конструкторе. Однако это не всегда удобно. Например, свойства могут инициализироваться с помощью внедрения зависимостей или в методе настройки модульного теста.
Для таких случаев пометьте свойство модификатором lateinit:
public class OrderServiceTest {
lateinit var orderService: OrderService
@SetUp fun setup() {
orderService = OrderService()
}
@Test fun processesOrderSuccessfully() {
// Calls orderService directly without checking for null
// or initialization
orderService.processOrder()
}
}
Модификатор lateinit можно использовать для свойств var, объявленных как:
Свойства верхнего уровня.
Локальные переменные.
Свойства в теле класса.
Для свойств класса:
Их нельзя объявлять в первичном конструкторе.
У них не должно быть пользовательского геттера или сеттера.
Во всех случаях свойство или переменная должны быть допускающими значение null и не должны иметь примитивный тип.
Если обратиться к свойству lateinit до его инициализации, Kotlin выбросит специальное исключение, указывающее на используемое неинициализированное свойство:
class ReportGenerator {
lateinit var report: String
fun printReport() {
// Throws an exception as it's accessed before
// initialization
println(report)
}
}
fun main() {
val generator = ReportGenerator()
generator.printReport()
// Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property report has not been initialized
}
Чтобы проверить, инициализировано ли уже свойство lateinit var, используйте свойство isInitialized у ссылки на это свойство:
class WeatherStation {
lateinit var latestReading: String
fun printReading() {
// Checks whether the property is initialized
if (this::latestReading.isInitialized) {
println("Latest reading: $latestReading")
} else {
println("No reading available")
}
}
}
fun main() {
val station = WeatherStation()
station.printReading()
// No reading available
station.latestReading = "22°C, sunny"
station.printReading()
// Latest reading: 22°C, sunny
}
Использовать isInitialized для свойства можно, только если к этому свойству уже есть доступ в коде. Свойство должно быть объявлено в том же классе, во внешнем классе или как свойство верхнего уровня в том же файле.
Переопределение свойств
См. раздел «Переопределение свойств».
Делегированные свойства
Чтобы повторно использовать логику и сократить дублирование кода, можно делегировать отдельному объекту ответственность за получение и изменение значения свойства.
Делегирование поведения аксессоров позволяет централизовать логику доступа к свойству и упрощает её повторное использование. Такой подход полезен при реализации следующих сценариев:
Вычисление значения по требованию.
Чтение значения из карты по заданному ключу.
Доступ к базе данных.
Уведомление слушателя при обращении к свойству.
Эти распространённые сценарии можно реализовать самостоятельно в библиотеках или использовать уже существующие делегаты из сторонних библиотек. Подробнее см. раздел «Делегированные свойства».
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/properties.html