А в чем проблема?
В разработке бизнес-логики для интеграции различных информационных систем часто необходимо учитывать fallback-сценарии, при которых для данных одной системы может не найтись соответствия в другой. Иными словами, при обработке запроса другой системы возникает необходимость подставить некий объект-заглушку, если в базе данных нашей системы не нашлось нужного соответствия типов.
Так как объект, по сути, является константой (соответствующая ему запись в БД не меняется), то обращаться к БД каждый раз, когда нам нужно подставить заглушку, нерационально.
Например, в случае, когда для типа документа, пришедшего из внешней системы, не нашлось соответствие в нашей внутренней системе, используется тип-заглушка, которым помечаются все документы для их последующей обработки вручную.
private val stubDocumentTypeId: Long
get() = getDocumentTypeIdByExternalId(
DocumentType.STUB_EXTERNAL_ID
)
private fun getDocumentTypeIdByExternalId(documentTypeExternalId: UUID) =
documentTypeRepository.getByExternalId(documentTypeExternalId)?.id
?: throw DocumentException(
"DocumentType with external id $documentTypeExternalId not found"
)
Значение поля stubDocumentTypeId
будет вычисляться каждый раз, когда вызывается его геттер. В Kotlin, когда вы используете свойство с кастомным геттером, тело этого геттера выполняется при каждом доступе к свойству.
Если мы хотим, чтобы значение вычислялось только один раз и сохранялось без необходимости повторного доступа к репозиторию, можно использовать lazy
делегат. Это обеспечит ленивую инициализацию значения, которое будет вычислено только один раз при первом доступе, а затем сохранено для дальнейшего использования.
Вот как можно изменить код, чтобы использовать lazy
:
private val stubDocumentTypeId: Long by lazy {
getDocumentTypeIdByExternalId(
DocumentType.STUB_EXTERNAL_ID
)
}
Теперь значение поля stubDocumentTypeId
будет вычислено только один раз при первом доступе и сохранено для дальнейшего использования.
Пару слов про Lazy-делегаты
Lazy-делегаты в Kotlin предоставляют возможность отложенной инициализации свойства. Это означает, что значение свойства будет вычислено только при первом доступе к нему, а результат этого вычисления будет кэширован для последующего использования. Этот подход полезен для улучшения производительности и снижения затрат на создание объектов, особенно если инициализация свойства является ресурсоемкой.
Основные особенности lazy-делегатов:
- Отложенная инициализация: Значение свойства инициализируется только при первом доступе к нему.
- Кэширование: Значение вычисляется только один раз и сохраняется для последующего использования.
- Безопасность при многопоточности: По умолчанию lazy-делегаты безопасны для использования в многопоточных окружениях.
Синтаксис и использование
Для создания lazy-делегата используется функция lazy { ... }
, которая принимает лямбда-выражение с инициализационным кодом:
val myLazyValue: String by lazy {
println("Initializing...")
"Hello, World!"
}
fun main() {
println("Before accessing myLazyValue")
println(myLazyValue) // В первый раз будет напечатано "Initializing..."
println(myLazyValue) // Во второй раз "Initializing..." не будет напечатано, так как значение уже инициализировано
}
Параметры инициализации
Функция lazy
имеет три возможных режима инициализации:
-
SYNCHRONIZED (по умолчанию): Инициализация безопасна для многопоточных окружений. Используется synchronized
для обеспечения потокобезопасности.
val myLazyValue: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
"Hello, World!"
}
-
PUBLICATION: Значение может быть инициализировано в нескольких потоках, но первый инициализатор выиграет, и его значение будет использовано.
val myLazyValue: String by lazy(LazyThreadSafetyMode.PUBLICATION) { "Hello, World!" }
-
NONE: Не обеспечивает потокобезопасность. Подходит для использования в однопоточных окружениях.
val myLazyValue: String by lazy(LazyThreadSafetyMode.NONE) { "Hello, World!" }
Еще немного примеров использования
Кроме описанного выше использования lazy-делегатов в пользовательских классах сервисов и репозиториев, они могут быть полезны в следующих случаях.
Ленивое чтение конфигурационного файла
val config: Properties by lazy {
val properties = Properties()
properties.load(FileInputStream("config.properties"))
properties
}
fun main() {
println(config.getProperty("someKey"))
}
Ленивое создание сложного объекта
class MyClass {
val complexObject: ComplexObject by lazy {
println("Creating ComplexObject...")
ComplexObject()
}
}
fun main() {
val myClass = MyClass()
println("Before accessing complexObject")
println(myClass.complexObject) // В первый раз будет напечатано "Creating ComplexObject..."
println(myClass.complexObject) // Во второй раз объект уже будет создан
}
Таким образом, Lazy-делегаты в Kotlin предоставляют мощный и удобный механизм для отложенной инициализации свойств, что позволяет улучшить производительность и управление ресурсами в приложении.