翻译说明:html
原标题: How Kotlin’s delegated properties and lazy-initialization workjava
原文地址: medium.com/til-kotlin/…git
原文做者: Chang W. Dohgithub
在支持面向对象范式的编程语言中,相信你们对访问属性应该很是熟悉了吧。Kotlin就提供了不少这样的方法,经过by lazy
实现属性的懒加载就是一个很好的例子。算法
在这篇文章中,咱们将一块儿去看看如何使用Kotlin中的委托属性以及by lazy
的懒加载而后深刻了解它们内部的工做原理,一步步揭开它们语法糖衣。编程
我认为大家中不少人对nullable
已经了然于胸,可是让咱们再来看看它。咱们使用Kotlin来开发Android时你可能会像以下这样写:设计模式
class MainActivity : AppCompatActivity() {
private var helloMessage : String = "Hello"
}
复制代码
在上述例子中,在对象建立的时候就初始化,这也没什么大的问题。然而,若是在特定的初始化过程以后引用它,则不能提早声明和使用值,由于它有本身的生命周期来初始化自身。数组
让咱们一块儿来看下一些熟悉Java代码缓存
public class MainActivity extends AppCompatActivity {
private TextView mWelcomeTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWelcomeTextView = (TextView) findViewById(R.id.msgView);
}
}
复制代码
你可使用Kotlin来实现上述代码,经过将上述mWelcomeTextView
声明成可空类型就能够了.安全
class MainActivity : AppCompatActivity() {
private var mWelcomeTextView: TextView? = null//声明成可空类型
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mWelcomeTextView = findViewById(R.id.msgView) as TextView
}
}
复制代码
上面那个例子代码运行良好,可是在代码中使用属性的以前每次都须要检查它是否为null就显得难受了。这一点你彻底可使用非空类型实现它。
class MainActivity: AppCompatActivity () {
private var mWelcomeTextView: TextView
...
}
复制代码
固然上述代码,你须要使用lateinit来告诉编译器,你将稍后为组件
mWelcomeTextView
初始化值。
与咱们一般讨论的延迟初始化(lazy initialization) 不一样的是,lateinit
容许编译器识别非空类型属性的值不存储在构造函数阶段以至于能够正常编译。
class MainActivity : AppCompatActivity() {
private lateinit var mWelcomeTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mWelcomeTextView = findViewById(R.id.msgView) as TextView
}
}
复制代码
若是你想要了解更多,请查看这里
一般,若是组件的字段不是基本数据类型或者内置类型,则能够发现引用是保留在组件整个生命周期中的。
例如,在Android应用程序中,大多数的组件引用是在
Acitivity
生命周期中保持不变的。换句话说,这就意味着你不多须要更改组件的引用。
基于这一点,咱们能够很容易想到如下这点:
“若是属性的值一般保留在组件的生命周期中,那么只读类型的属性是否足以保持该值?”
我认为能够的,要作到这一点,乍看一眼只需将var
改成val
一点改动就能够了。
可是,当咱们声明只读属性时,咱们面临的问题是没法定义执行初始化的位置
class MainActivity : AppCompatActivity() {
private val mWelcomeTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Where do I move the initialization code????? 我应该把这个初始化的代码移到哪呢???
// mWelcomeTextView = findViewById(R.id.msgView) as TextView
}
}
复制代码
如今让咱们尝试解决最后一个问题:
“咱们在哪初始化只读属性呢”
当实如今Kotlin中执行延迟初始化的只读属性是,by lazy
也许就特别有用了。
by lazy{...}
执行初始化程序,其中首先使用的是定义的属性,而不是它的声明。
class MainActivity : AppCompatActivity() {
private val messageView : TextView by lazy {
// 下面这段代码将在第一次访问messageView时执行
findViewById(R.id.message_view) as TextView
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun onSayHello() {
// 真正初始化将会在这执行!!
messageView.text = "Hello"
}
}
复制代码
如今,咱们能够声明一个只读属性,而没必要担忧messageView
的初始化点的问题。让咱们看看懒加载背后原理是怎么样的。
Delegation的意思就是委托。它意味着经过委托者能够执行某些操做,而不是直接经过原始访问者执行操做。
属性委托是委托属性的getter/setter
方法,它容许委托对象在读取和写入值时插入执行一些中间操做。
Kotlin将支持接口(类委托)或访问器(委托属性)的实现委托给另外一个对象。
你能够经过by <delegate>
形式来声明一个委托属性
val / var <property name>: <Type> by <delegate>
复制代码
属性的委托能够像以下方式定义:
class Delegate {
operator fun getValue( thisRef: Any?, property: KProperty<*> ): String {
// return value
}
operator fun setValue( thisRef: Any?, property: KProperty<*>, value: String ) {
// assign
}
}
复制代码
对值的全部读取操做都会委托调用getValue()
方法,同理,对值的全部写操做都会委托调用setValue()
方法。
如今让咱们再次从新研究下上述例子中属性的代码。
它实际上就是一个属性委托!
咱们能够把by lazy
修饰的属性理解为是具备lazy
委托的委托属性。
因此,lazy
是如何工做的呢? 让咱们一块儿在Kotlin标准库参考中总结lazy()
方法,以下所示:
lazy()
返回的是一个存储在lambda初始化器中的Lazy<T>
类型实例。lazy()
的lambda并存储其结果。简单地说,lazy建立一个实例,在第一次访问属性值时执行初始化,存储结果并返回存储的值。
lazy()
的委托属性让咱们编写一个简单的Kotlin代码来检查lazy
的实现。
class Demo {
val myName: String by lazy { "John" }
}
复制代码
若是你将其反编译为Java代码,则能够看到如下代码:
public final class Demo {
@NotNull
private final Lazy myName$delegate;
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = ...
@NotNull
public final String getMyName() {
Lazy var1 = this.myName$delegate;
KProperty var3 = $$delegatedProperties[0];
return (String)var1.getValue();
}
public Demo() {
this.myName$delegate =
LazyKt.lazy((Function0)null.INSTANCE);
}
}
复制代码
$delegate
后缀被拼接到字段名称后面: myName$delegate
myName$delegate
的类型是Lazy
类型不是String
类型LazyKt.lazy()
函数返回值赋值给了myName$delegate
LazyKt.lazy()
方法负责执行指定的初始化块调用getMyName()
方法实际过程是将经过调用myName$delegate
的Lazy
实例中的getValue()
方法并返回相应的值。
lazy()
方法返回的是一个Lazy<T>
类型的对象,该对象处理lambda函数(初始化程序块),根据线程执行模式(LazyThreadSafetyMode)以稍微几种不一样的方式执行初始化。
@kotlin.jvm.JvmVersion
public fun <T> lazy( mode: LazyThreadSafetyMode, initializer: () -> T
): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED ->
SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION ->
SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE ->
UnsafeLazyImpl(initializer)
}
复制代码
全部这些都负责调用给定的lambda块进行延迟初始化
SYNCHRONIZED → SynchronizedLazyImpl
PUBLICATION → SafePublicationLazyImpl
NONE → UnsafeLazyImpl
SynchronizedLazyImpl
和SafePublicationLazyImpl
,UnsafeLazyImpl
经过如下过程执行延迟初始化。咱们来看看前面的例子。
initializer
中_value
来存储值。此属性最开始初始值为UNINITIALIZED_VALUE
。_value
的值是最开始初始值UNINITIALIZED_VALUE
,那么就会去执行 initializer
初始化器_value
的值不是等于UNINITIALIZED_VALUE
, 那就说明初始化操做已经执行完成了。若是你没有明确指定具体模式,延迟具体实现就是SynchronizedLazyImpl
,它默认只执行一次初始化。咱们来看看它的实现代码。
private object UNINITIALIZED_VALUE
private class SynchronizedLazyImpl<out T>(
initializer: () -> T,
lock: Any? = null
) : Lazy<T>,
Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required
// to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
}
else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean =
_value !== UNINITIALIZED_VALUE
override fun toString(): String =
if (isInitialized()) value.toString()
else "Lazy value not initialized yet."
private fun writeReplace(): Any =
InitializedLazyImpl(value)
}
复制代码
这看起来有点复杂。但它只是多线程的通常实现。
synchronized()
同步块执行初始化块initializer
将会置为null
,由于初始化完成后就再也不须要它了。固然,延迟初始化有时会致使问题发生或经过绕过控制流并在异常状况下生成正常值来使调试变得困难。
可是,若是你对这些状况很是谨慎,那么Kotlin的延迟初始化可使咱们更加自由地避免对线程安全性和性能的担心。
咱们还研究了延迟初始化是运算符 by
和lazy
函数的共同做用的结果。还有更多的委托,如Observable
和notNull
。若有必要,你还能够实现有趣的委托属性。
闲聊几句,有一些小伙伴在私下问我对近期2019 Google IO 大会宣布Kotlin为Android开发首选语言,即Kotlin-First.这件事怎么看? 其实我的对于这个比较日常心,以为不是什么特别新奇的事,说真的Kotlin自身有能力和优点充当这么个角色。若是你还在犹豫要不要学Kotlin这门语言的时候,那你就去看看Google IO使用Android代码例子几乎全是Kotlin编写,并且后面不少官方的框架和库都是Kotlin编写。我们仍是好好扎实把Kotlin中每一个细节点吃透吧,那么你就会真正领会到Kotlin这门语言的设计哲学了。
说下为何要翻译这篇文章?
这篇文章能够说把by lazy
属性懒加载的从使用场景到原理剖析都阐述的很是清楚,并以图表形式把by lazy
调用过程逻辑呈现得简单易懂。
你是否在Kotlin代码使用过by lazy
呢?那你知道何时该使用lateinit
,何时使用by lazy
吗?实际上这篇文章已经给出答案了,这里简单大体说下: by lazy
正如文章中说的那样用于非空只读属性,须要延迟加载状况,而lateinit
通常用于非空可变属性,须要延迟加载状况。后续会有相关文章详细阐述它们区别以及使用场景。
其实这篇文章还涉及到一个点,那就是 by lazy
中的SynchronizedLazyImpl
,实际上经过反编译后代码可知它是DCL(double check lock),因此能够利用Companion Object
+ by lazy
能够实现Kotlin中的DCL单例模式。具体可参考我以前那篇文章当Kotlin完美邂逅设计模式之单例模式(一)
Kotlin邂逅设计模式系列:
数据结构与算法系列:
翻译系列:
原创系列:
Effective Kotlin翻译系列
实战系列: