在收到本系列前两部分中关于 Kotlin 编程语言(包括杰克·沃顿本人提及)的积极反馈之后,我积极开展进一步的调研。不要错过 part 1 和 part 2。
在第 3 部分中,我们将展示 Kotlin 编译器的更多秘密,并提供编写更有效代码的新技巧。
代理属性可以这样解释,该属性的 getter 和可选 setter 实现是由一个称为 delegate 的外部对象所提供的。 这允许可重用的自定义属性实现。
class Example { var p: String by Delegate() }
delegate 对象必须实现一个操作符 getValue() 函数,以及一个用于读/写属性的 setValue() 函数。这些函数将作为额外的参数传递给其所包含的对象实例以及属性元数据(就跟它的名字一样)。
当类中定义了 delegated 属性,由编译器生成以下与 Java 匹配的代码:
public final class Example { @NotNull private final Delegate p$delegate = new Delegate(); // $FF: synthetic field static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Example.class), "p", "getP()Ljava/lang/String;"))}; @NotNull public final String getP() { return this.p$delegate.getValue(this, $$delegatedProperties[0]); } public final void setP(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.p$delegate.setValue(this, $$delegatedProperties[0], var1); } }
一些静态属性元数据被添加到类中。 委托在类构造函数中初始化,然后在每次读取或写入该属性时调用它。
在上面的例子中,创建一个新的代理实例来实现该属性。代理实现必须是有状态的,例如,如果它在本地缓存了该属性的计算值:
class StringDelegate { private var cache: String? = null operator fun getValue(thisRef: Any?, property: KProperty<*>): String { var result = cache if (result == null) { result = someOperation() cache = result } return result } }
它还需要创建一个新的代理实例,如果它需要额外的参数,则需要通过它的构造函数:
class Example { private val nameView by BindViewDelegate<TextView>(R.id.name) }
但是在某些情况下,只需要一个代理实例来实现任何属性:当代理是无状态的,并且唯一需要做的工作的变量是已经提供的包含对象实例和属性名称。在这种情况下,您可以通过将代理声明为对象而不是类来使其成为单例。
例如,以下单例代理检索其标签名称与 Android Activity 中的属性名称相匹配的代码片段:
object FragmentDelegate { operator fun getValue(thisRef: Activity, property: KProperty<*>): Fragment? { return thisRef.fragmentManager.findFragmentByTag(property.name) } }
类似地,任何现有的对象都可以扩展成为一个代理。 getValue() 和 setValue() 也可以被声明为扩展函数。 Kotlin 已经提供了内置的扩展功能,可以将 Map 和 MutableMap 实例作为代理,使用属性名称作为键。
如果您选择重用同一本地代理实例来实现同一个类中的多个属性,则需要在类构造函数中初始化此实例。
注意:由于 Kotlin 1.1,还可以在一个函数中声明一个局部变量作为代理属性。 在这种情况下,代理可以稍后被初始化,直到变量在函数中被声明。
在类中声明的每个代理属性还涉及其关联的代理对象的开销,并向该类添加一些元数据。
当有意义的时候,尝试重用代理代表不同的属性。
还要考虑在需要声明大量的属性的情况下,代理属性是否是最佳选择。
委托函数可以用泛型的方式声明,因此同一个委托类可以用于不同的属性类型。
private var maxDelay: Long by SharedPreferencesDelegate<Long>()
然而,如果你像上面的例子那样对基本类型属性使用泛型委托,每次读写属性都会装箱和拆箱,即使声明的基本类型是非空的。
对于非空基本类型属性的委托,最好使用专门为值类型创建的特定的委托类,而不要使用泛型委托,这样可以避免每次访问属性时都进行装箱操作。
Kotlin 提供了一些标准的委托,它们涵盖常规用途,比如 Delegates.notNull()、Delegates.observable() 和 lazy()。
lazy() 是一个返回只读属性委托的函数,它会在首次读取属性时执行提供的 Lambda 来初始化属性。
private val dateFormat: DateFormat by lazy { SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) }
这是一种干净利落的方法,用于将昂贵的初始化操作延迟到确实需要的时候,它可以在保持代码可读性的同时提供性能。
应该指出的是,lazy() 不是内联函数,作为参数传递给它的 Lambda 会被编译成另一个函数类,并且不会内联到返回的委托对象中。
经常被忽视的是, lazy() 有一个可选的 mode 参数以确定返回 3 个不同类型的代表中的某一个:
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) }
默认的模式 LazyThreadSafetyMode.SYNCHRONIZED 将执行相对昂贵的双重检查锁定,以确保当可以从多个线程读取该属性时,初始化块将安全地运行一次。
如果你知道一个属性将只从单个线程(如主线程)访问,则可以通过明确地使用 LazyThreadSafetyMode.NONE 来完全避免锁定:
val dateFormat: DateFormat by lazy(LazyThreadSafetyMode.NONE) { SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) }
使用 lazy() 代表来推迟昂贵的初始化,并指定线程安全模式,以避免在不需要时锁定。
在 Kotlin 中,Ranges 是用于表示有限集合的特殊表达式。这些值可以是任何 Comparable 的类型。这些表达式由用于创建实现了 ClosedRange 接口的对象的函数组成。用于创建 range 的主要函数是 .. 操作符函数。
range 表达式的主要目的是在操作符中使用 in 和 !in 编写包含或排除测试。
if (i in 1..10) { println(i) }
其实现针对非空原始类型的 range 做了优化(范围限定在 Int,Long,Byte,Short,Float,Double 或 Char 值),以便上述示例高效地编译为:
if(1 <= i && i <= 10) { System.out.println(i); }本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务