The power of lazy properties in Kotlin

栏目: IT技术 · 发布时间: 4年前

内容简介:Do you have properties in your Kotlin types that you want to create later after initialization? You may want to avoid making them nullable, avoid usingThe idea of making a propertyWhen it comes to defining lazy properties in Kotlin, they have been brought

Do you have properties in your Kotlin types that you want to create later after initialization? You may want to avoid making them nullable, avoid using lateinit or delay an expensive set up process to a later point. In Kotlin this is where delegated properties and lazy properties in particular can be used, allowing properties to be initialized when they are needed and even keep our main type’s initialization simpler and cleaner. :+1:

The idea of making a property lazy is not new to Kotlin and many developers having been doing it in other languages or doing it themselves for ages. There were common approaches to a form of lazy creation in Java, it was hard to live in the Java world without coming across a getInstance() function at some point!

When it comes to defining lazy properties in Kotlin, they have been brought into being part of the language. There is some new syntax, some different options for initializing them and various situations in which they can be really useful. Let’s take a look! :eyes:

The power of lazy properties in Kotlin
Initialize now or later?

Creating them

Creating a lazy property in Kotlin is pretty simple, we define it using by lazy and provide a function initializer. At a later point, when the property is first accessed it will be initialized using the function we provided and then on future accesses the cached value will be returned instead. Nifty!

private val bindingController by lazy { 
    FilesBindingController(viewModel::onFileOpened) 
}

Our bindingController property needs to access the viewModel property in order to be created and so by making it lazy, this can be deferred until it is first used. After bindingController has been initialized, the value will simply be returned every time from then on.

Factory method

Creating our property may not always be as simple as just calling an initializer and more code may be needed to set it up. In these situations we may wish to move the contents of the lazy function initializer to a factory method on the outer class.

class TeamRepository(appSchedulers: AppSchedulers) {
  private val viewState by lazy(::createViewStateLiveData)

  private fun createViewStateLiveData(): LiveData<ViewState> =
      teamRepository.teamMembersStream()
          .map(::mapPresentingState)
          .onErrorReturn(::mapErrorState)
          .startWith(ViewState.Loading)
          .subscribeOn(appSchedulers.io)
          .observeOn(appSchedulers.main)
          .toLiveData()
}

Moving the initialization code to a factory method can keep the top of our class cleaner and we may find we prefer to keep this set up code out of the way. In some situations we may even be able to use a separate factory object to move the initialization logic to another type or file if we wanted to.

Extension

Across our codebase there will be common situations for using lazy properties and so to simplify the call sites we may decide to use an extension function. For example, when using Android ViewModel , each of our Activities will need to retrieve their View Model, which can be done using an extension function on a common base class.

inline fun <reified ViewModelT : ViewModel> ComponentActivity.bindViewModel() = 
    bindViewModel(ViewModelT::class, viewModelFactoryProvider)

@PublishedApi
internal fun <ViewModelT : ViewModel> ComponentActivity.bindViewModel(
    viewModelType: KClass<ViewModelT>
): Lazy<ViewModelT> = lazy {
    ViewModelProvider(this).get(viewModelType.java)
}

Using our new lazy extension function cleans up the initialization call site.

class TeamActivity : FragmentActivity() {
  private val viewModel: TeamViewModel by bindViewModel()
}

This particular example is now provided by the AndroidX Activity library .

How does it work?

Delegated properties work as their name suggests, delegating the property’s getter and maybe setter to another type. There are a selection of different delegated property types provided and then on top of this we can write our own. The expression we place after the by keyword is what specifies the delegate that will be used.

In the case of by lazy , a Lazy delegate type will control access to our property, initializing it on first access and then returning the cached value on subsequent accesses. Users of the property don’t need to change how they call it, meaning the read-write behaviour of our property can be changed at the property definition without affecting code that uses it.

Multithreading

The lazy function has an argument with a default value that controls its synchronization behaviour. If a lazy property is accessed from multiple threads concurrently, synchronization will need to be handled by choosing an appropriate LazyThreadSafetyMode .

private val messageId by lazy(LazyThreadSafetyMode.NONE) { createMessageId() }

The default value SYNCHRONIZED will ensure only a single thread can initialize the property using locks. If we are sure the property will only be accessed by a single thread we can switch to NONE to avoid the overhead of performing the synchronization. There is also the option of using PUBLICATION which allows multiple threads to call the initializer, but only the first returned value being used.

Most UI code, such as in an Activity or Fragment , will run on the UI thread and so properties that are only used here can use the LazyThreadSafetyMode.NONE . We could even add an extension to avoid specifying this each time.

Other use cases

Lazy is useful wherever we want to delay initialization to a later point, rather than doing it straight away. Without it other options may include a lateinit property which simply “promises” to be initialized before it is used or a nullable property which can have a value or not.

When we just want to delay initialization, lazy can be a nicer approach to the alternatives. It can be especially helpful for obtaining dependencies whose initialization is out of our control, such as Android View Models from an Activity or Fragment.

There are times when we need to read a value from somewhere, but don’t want it to be retrieved again each time it is needed. An example is reading Bundle extras from an Intent , where using a lazy property would mean on first access the value is read from the Intent and then cached for quicker access in the future.

class OrderDetailActivity : FragmentActivity() {
  val orderId by lazy { 
      intent.getParcelableExtra<OrderId>(EXTRA_ORDER_ID) 
  }
}

Wrap up

Delegated properties and lazy properties in particular are a great feature to have at our disposal. The give us flexibility over how our properties are initialized and accessed. It is really nice to be able to make a property lazy without having to alter how it is used in calling code. :pray:

Do you use lazy properties in your Kotlin code? What do you think of them? Are there any interesting things about them or nice use cases that I haven’t mentioned? Please reach out on Twitter @lordcodes with any questions or thoughts you have, or about anything else.

If you like what you have read, please don’t hesitate to share the article andsubscribe to my feed if you are interested.

Thanks for reading and happy coding! :pray:


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

人月神话

人月神话

[美] 弗雷德里克·布鲁克斯 / 汪颖 / 清华大学出版社 / 2002-11 / 29.80元

作者为人们管理复杂项目提供了颇具洞察力的见解,既有很多发人深省的观点,也有大量的软件工程实践。书中的内容来自布鲁克斯在IBM公司System 360家族和OS 360中的项目管理经验。初版的20年后,布鲁克斯重新审视了他原先的观点,增加了一些新的想法和建议。新增加的章节包括:原著中一些核心观点的精华;在经过了一个时代以后,Brooks博士对原先观点新的认识;1986年的经典文章《没有银弹》;对19......一起来看看 《人月神话》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具