本篇文章来自willwaywang6的投稿,文章详细分析了Kotlin中by关键字,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。
willwaywang6的博客地址:
https://blog.csdn.net/willway_wang
/ 前言 /
Kotlin 中的 by 关键字在 Java 中是没有的,这使我对它感到非常陌生。
Kotlin 中为什么要新增 by 关键字呢?by 关键字在 Kotlin 中是如何使用的?
本文会介绍 by 关键字的使用分类,具体的示例,Kotlin 内置的 by 使用,希望能够帮助到大家。
by 关键字的使用分为两种:类委托 和委托属性 。
/ 类委托 /
现在有一个需求,统计向一个 HashSet 尝试添加元素的尝试次数,该怎么实现?
使用继承方式实现 简单,继承 一个 HashSet,创建一个变量,负责统计尝试添加元素的个数,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class CountingSet1 <T >: HashSet <T > () { var objectAdded = 0 override fun add (element: T ) : Boolean { objectAdded++ return super .add(element) } override fun addAll (elements: Collection <T >) : Boolean { return super .addAll(elements) } }fun main () { val cset = CountingSet1<Int >() cset.add(1 ) cset.addAll(listOf(2 , 2 , 3 )) println("${cset.objectAdded} objects were added, ${cset.size} remain" ) }
需求是满足了。
但是这样的实现,CountingSet1 和 HashSet 的具体实现是高度耦合的,也就是说,CountingSet1 严重依赖于 HashSet 类的实现细节。
这有什么问题吗?
当基类的实现被修改或者新的方法被添加进去时,可能改变之前进行继承时的类行为,从而导致子类的行为不符合预期。
当某个类是 final 类时,它是不可以被继承的,这时就不能采用继承的方式来复用它的代码了。
那我们该怎么办呢?
想一下,我们新建的类无非是想复用 HashSet的功能,前面我们是采用继承的方式,除了采用继承的方式之外,我们还可以采用组合的方式。
实际上,在 《Java编程思想》中有写道:
由于继承在面向对象程序设计中如此重要,所以它经常被高度强调,于是程序员新手就会有这样的印象:处处都应该使用继承。这会导致难以使用并过分复杂的设计。实际上,在建立新类时,应该优先考虑组合,因为它更加简单灵活。 如果采用这种方式,设计会变得更加清晰。
使用组合方式实现 使用组合 方式实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 class CountingSet2 <T > : MutableSet <T > { private val innerSet = HashSet<T>() var objectAdded = 0 override fun add (element: T ) : Boolean { objectAdded++ return innerSet.add(element) } override fun addAll (elements: Collection <T >) : Boolean { objectAdded += elements.size return innerSet.addAll(elements) } override fun clear () { innerSet.clear() } override fun iterator () : MutableIterator<T> { return innerSet.iterator() } override fun remove (element: T ) : Boolean { return innerSet.remove(element) } override fun removeAll (elements: Collection <T >) : Boolean { return innerSet.removeAll(elements) } override fun retainAll (elements: Collection <T >) : Boolean { return innerSet.retainAll(elements) } override val size: Int get () = innerSet.size override fun contains (element: T ) : Boolean { return innerSet.contains(element) } override fun containsAll (elements: Collection <T >) : Boolean { return innerSet.containsAll(elements) } override fun isEmpty () : Boolean { return innerSet.isEmpty() } }fun main () { val cset = CountingSet2<Int >() cset.add(1 ) cset.addAll(listOf(2 , 2 , 3 )) println("${cset.objectAdded} objects were added, ${cset.size} remain" ) }
同样实现了需求。
可以看到,CountingSet2 实现了 MutableSet 接口,具体的实现是委托给了 innerSet 来完成的。
这有什么好处呢?
CountingSet2 与 HashSet 不再耦合了,它们都实现了 MutableSet 接口。
这种方式从代码设计上看确实好,但是却需要非常多的模板代码,这点很烦人啊。
使用类委托实现 现在就该 Kotlin 的类委托出场了,它可以解决需要写非常多的模板代码的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class CountingSet3 <T > ( val innerSet: MutableSet<T> = HashSet<T>() ) : MutableSet<T> by innerSet { var objectAdded = 0 override fun add (element: T ) : Boolean { objectAdded++ return innerSet.add(element) } override fun addAll (elements: Collection <T >) : Boolean { objectAdded += elements.size return innerSet.addAll(elements) } }fun main () { val cset = CountingSet3<Int >() cset.add(1 ) cset.addAll(listOf(2 , 2 , 3 )) println("${cset.objectAdded} objects were added, ${cset.size} remain" ) }
完美实现了需求。
Kotlin 是如何帮我们减少了模板代码了呢?
使用 Android Studio 的 Tools -> Kotlin -> Show Kotlin Bytecode,再点击 Decompile 按钮,查看对应的 Java 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 public final class CountingSet3 implements Set , KMutableSet { private int objectAdded; @NotNull private final Set innerSet; public final int getObjectAdded ( ) { return this .objectAdded; } public final void setObjectAdded (int var1 ) { this .objectAdded = var1; } public boolean add (Object element ) { int var10001 = this .objectAdded++; return this .innerSet.add(element); } public boolean addAll (@NotNull Collection elements ) { Intrinsics.checkNotNullParameter(elements, "elements" ); this .objectAdded += elements.size(); return this .innerSet.addAll(elements); } @NotNull public final Set getInnerSet ( ) { return this .innerSet; } public CountingSet3 (@NotNull Set innerSet ) { Intrinsics.checkNotNullParameter(innerSet, "innerSet" ); super (); this .innerSet = innerSet; } public CountingSet3 (Set var1, int var2, DefaultConstructorMarker var3 ) { if ((var2 & 1 ) != 0 ) { var1 = (Set )(new HashSet()); } this (var1); } public CountingSet3 ( ) { this ((Set )null , 1 , (DefaultConstructorMarker)null ); } public int getSize ( ) { return this .innerSet.size(); } public final int size ( ) { return this .getSize(); } public void clear ( ) { this .innerSet.clear(); } public boolean contains (Object element ) { return this .innerSet.contains(element); } public boolean containsAll (@NotNull Collection elements ) { Intrinsics.checkNotNullParameter(elements, "elements" ); return this .innerSet.containsAll(elements); } public boolean isEmpty ( ) { return this .innerSet.isEmpty(); } @NotNull public Iterator iterator ( ) { return this .innerSet.iterator(); } public boolean remove (Object element ) { return this .innerSet.remove(element); } public boolean removeAll (@NotNull Collection elements ) { Intrinsics.checkNotNullParameter(elements, "elements" ); return this .innerSet.removeAll(elements); } public boolean retainAll (@NotNull Collection elements ) { Intrinsics.checkNotNullParameter(elements, "elements" ); return this .innerSet.retainAll(elements); } public Object [] toArray ( ) { return CollectionToArray.toArray(this ); } public Object [] toArray (Object [] var1 ) { return CollectionToArray.toArray(this , var1); } }
这不就是 CountingSet2 的代码吗?
原来 Kotlin 的编译器默默地帮我们生成了这些模板代码,而仅仅要求我们通过声明并初始化一个 HashSet 类型的成员变量,并在类声明后添加 by innerSet:
1 2 3 class CountingSet3 <T >( val innerSet: MutableSet <T > = HashSet <T >() ) : MutableSet <T > by innerSet {
需要注意的是:
CountingSet3 必须实现一个接口,而不能继承于一个类;
innerSet 的类型必须是 CountingSet3 所实现接口的子类型;
可以直接在 by 创建委托对象,如下所示:
1 2 3 class CountingSet4 <T >( ) : MutableSet <T > by HashSet <T >() { }
但是,这样的话,在 CountingSet4 类中无法获取到委托对象的引用了。
Kotlin 的类委托虽然看起来很简洁,但是它自身又有一些限制:类必须实现一个接口,委托类必须是类所实现接口的子类型。这是需要注意的。
同时,我们在实际开发中,要尽力去使用这种委托的思想,来使代码解耦,使代码更加清晰 。这一点,比掌握 Kotlin 的类委托更加重要。
/ 委托属性 /
委托属性是一个依赖于约定的功能,也是Kotlin 中最独特和最强大的功能之一。
本部分我们仍然是从一个小例子开始,展示一下委托属性的使用,作用;然后,会说明委托属性的一些特点以及其他使用。
需求:现在有一个简单的 Person 类:
1 2 3 4 class Person2 { var name: String = "" var lastname: String = "" }
需要对 name 和 lastname 赋值时,做一些格式化工作:首字母大写其余字母小写,并统计格式化操作的次数,再获取 name 和 lastname 值的时候,把它们的值的长度拼接在值得后面返回。
仅仅完成需求的代码 这不难,代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Person2 { var name: String = "" set (value) { field = value.lowercase().replaceFirstChar { it.uppercase() } updateCount++ } get () { return field + "-" + field.length } var lastname: String = "" set (value) { field = value.lowercase().replaceFirstChar { it.uppercase() } updateCount++ } get () { return field + "-" + field.length } var updateCount = 0 }fun main () { val person2 = Person2() person2.name = "peter" person2.lastname = "wang" println("name=${person2.name} " ) println("lastname=${person2.lastname} " ) println("updateCount=${person2.updateCount} " ) }
ps:这里面使用 Kotlin 中的支持字段 field ,需要学习可以查看笔者的这篇文章:Kotlin 的 Backing Fields 和 Backing Properties (https://blog.csdn.net/willway_wang/article/details/100184784)。
OK,查看日志,可以看到需求实现了。
抽取重复代码为方法 但是,这里面有着重复的代码,并且如果其他类也需要这样的格式化操作,这些代码也不可以复用。
为了提供代码复用性,我们可以把代码抽取出来,放在一个方法里面。代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Person3 { var name: String = "" set (value) { field = format(value) } get () { return getter(field) } var lastname: String = "" set (value) { field = format(value) } get () { return getter(field) } var updateCount = 0 fun format (value: String ) : String { updateCount++ return value.lowercase().replaceFirstChar { it.uppercase() } } fun getter (value: String ) : String { return value + "-" + value.length } }fun main () { val person = Person3() person.name = "peter" person.lastname = "wang" println("name=${person.name} " ) println("lastname=${person.lastname} " ) println("updateCount=${person.updateCount} " ) }
查看日志打印,这样做可以实现需求。
使用类来封装重复代码 现在需求又来了,有一个 Student 类:
1 2 3 4 5 class Student { var name: String = "" var address: String = "" }
也需要对属性做同样的格式化操作并统计进行格式化操作的次数,在获取值的时候把长度拼接在后面。
这时,我们可以使用面向对象的思想来解决,把 format 方法和 getter 方法封装在一个类里面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Delegate { fun format (thisRef: Any , value: String ) : String { if (thisRef is Person4) { thisRef.updateCount++ } else if (thisRef is Student2) { thisRef.updateCount++ } return value.lowercase().replaceFirstChar { it.uppercase() } } fun getter (value: String ) : String { return value + "-" + value.length } }
Person 类修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Person4 { private val delegate = Delegate() var name: String = "" set (value ) { field = delegate.format(this , value) } get ( ) { return delegate.getter(field) } var lastname: String = "" set (value ) { field = delegate.format(this , value) } get ( ) { return delegate.getter(field) } var updateCount = 0 }
Student 类修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Student2 { private val delegate = Delegate() var name: String = "" set (value ) { field = delegate.format(this , value) } get ( ) { return delegate.getter(field) } var address: String = "" set (value ) { field = delegate.format(this , value) } get ( ) { return delegate.getter(field) } var updateCount = 0 }
把值的存储委托给委托类处理 既然 getter 和 setter 方法都委托给 Delegate 类来实现,我们何不把值也交给 Delegate 类来存储呢?
如果把 name 和 lastname 的值存在 Delegate 里面的话,它们就不可以共用一个 Delegate 对象了。
然后,Delegate 类两个方法的作用就是设置和获取值,所以方法也需要改一下。对吧?
修改 Delegate 类为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Delegate2 { var formattedString: String = "" fun setValue (thisRef: Any , value: String ) { if (thisRef is Person5) { thisRef.updateCount++ } else if (thisRef is Student2) { thisRef.updateCount++ } formattedString = value.lowercase().replaceFirstChar { it.uppercase() } } fun getValue () : String { return formattedString + "-" + formattedString.length } }
修改 Person 类为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Person5 { private val nameDelegate = Delegate2() private val lastnameDelegate = Delegate2() var name: String set (value) { nameDelegate.setValue(this , value) } get () { return nameDelegate.getValue() } var lastname: String set (value) { lastnameDelegate.setValue(this , value) } get () { return lastnameDelegate.getValue() } var updateCount = 0 }
到这里,需要强调的是,Person5 里的 name 和 lastname 不具有存储值的功能了,它们是自定义了 setter 和 getter 的属性。
使用 Kotlin 的委托属性来简化代码 对于上面的代码实现,我们仍然感到有些累赘,不简洁。
Kotlin 的委托属性可以帮我们解决这些烦恼。
委托属性的基本语法是这样的:
1 2 3 class Foo { var p : Type by Delegate () }
等价于
1 2 3 4 5 6 class Foo { private val delegate = Delegate() var p: Type set (value : Type ) = delegate .setValue(this , ..., value ) get () = delegate .getValue(this , ...) }
属性 p 将它的访问器逻辑委托给 Delegate 这个辅助对象来处理。
by 关键字的作用是对它后面的表达式求值来获取这个对象,在这里就是获取到了 Delegate 对象。
编译器会创建一个隐藏的辅助属性,并使用委托对象的实例对它进行初始化,在这里就是把 Delegate 对象赋值给了 delegate 属性。
那么,Kotlin 是如何知道把属性 p 的 setter 逻辑委托给辅助对象 delegate 的哪个方法,把属性 p 的 getter 逻辑委托给辅助对象 delegate 的哪个方法呢?
这里就要说到约定 的概念了,委托类必须具有 getValue 和 setValue 方法(如果是可变属性的话),定义在 ReadWriteProperty 接口里:
1 2 3 4 5 6 7 public fun interface ReadOnlyProperty<in T, out V> { public operator fun getValue (thisRef: T , property: KProperty <*>) : V }public interface ReadWriteProperty <in T, V > : ReadOnlyProperty <T, V > { public override operator fun getValue (thisRef: T , property: KProperty <*>) : V public operator fun setValue (thisRef: T , property: KProperty <*>, value: V ) }
当对 Foo 对象的 p 属性赋值时,会调用 Delegate 对象的 setValue 方法,设置对应的值;
当获取 Foo 对象的 p 的值时,会调用 Delegate 对象的 getValue 方法,获取对应的值。
修改委托类为:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Delegate3 : ReadWriteProperty <Any, String > { var formattedString = "" override fun getValue (thisRef: Any , property: KProperty <*>) : String { return formattedString + "-" + formattedString.length } override fun setValue (thisRef: Any , property: KProperty <*>, value: String ) { if (thisRef is Person6) { thisRef.updateCount++ } formattedString = value.lowercase().replaceFirstChar { it.uppercase() } } }
修改 Person 类为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Person6 { var name: String by Delegate3() var lastname: String by Delegate3() var updateCount = 0 }fun main () { val person = Person6() person.name = "peter" person.lastname = "wang" println("name=${person.name} " ) println("lastname=${person.lastname} " ) println("updateCount=${person.updateCount} " ) }
OK,完美符合需求。
使用 Android Studio 的 Tools -> Kotlin -> Show Kotlin Bytecode,再点击 Decompile 按钮,查看 Delegate3 和 Person6 对应的 Java 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 public final class Delegate3 implements ReadWriteProperty { @NotNull private String formattedString = "" ; @NotNull public final String getFormattedString() { return this .formattedString; } public final void setFormattedString(@NotNull String var1) { this .formattedString = var1; } @NotNull public String getValue(@NotNull Object thisRef, @NotNull KProperty property) { return this .formattedString + "-" + this .formattedString.length(); } public Object getValue(Object var1, KProperty var2) { return this .getValue(var1, var2); } public void setValue(@NotNull Object thisRef, @NotNull KProperty property, @NotNull String value) { if (thisRef instanceof Person6) { ((Person6)thisRef).setUpdateCount(((Person6)thisRef).getUpdateCount() + 1 ); } Delegate3 var10000 = this ; boolean var5 = false ; String var10001 = value.toLowerCase(Locale.ROOT); Intrinsics.checkNotNullExpressionValue(var10001, "(this as java.lang.Strin….toLowerCase(Locale.ROOT)" ); String var4 = var10001; var5 = false ; CharSequence var6 = (CharSequence)var4; boolean var7 = false ; if (var6.length() > 0 ) { StringBuilder var20 = new StringBuilder(); char it = var4.charAt(0 ); StringBuilder var15 = var20; int var9 = false ; boolean var11 = false ; String var12 = String.valueOf(it); boolean var13 = false ; if (var12 == null ) { throw new NullPointerException("null cannot be cast to non-null type java.lang.String" ); } String var19 = var12.toUpperCase(Locale.ROOT); Intrinsics.checkNotNullExpressionValue(var19, "(this as java.lang.Strin….toUpperCase(Locale.ROOT)" ); String var16 = var19; var10000 = this ; var20 = var15.append(var16.toString()); byte var17 = 1 ; boolean var18 = false ; if (var4 == null ) { throw new NullPointerException("null cannot be cast to non-null type java.lang.String" ); } String var10002 = var4.substring(var17); Intrinsics.checkNotNullExpressionValue(var10002, "(this as java.lang.String).substring(startIndex)" ); var10001 = var20.append(var10002).toString(); } else { var10001 = var4; } var10000.formattedString = var10001; } public void setValue(Object var1, KProperty var2, Object var3) { this .setValue(var1, var2, (String)var3); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public final class Person6 { static final KProperty[] $$delegatedProperties = new KProperty[]{ (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Person6.class , "name" , "getName()Ljava/lang/String;" , 0 )), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Person6.class , "lastname" , "getLastname()Ljava/lang/String;" , 0 ))}; @NotNull private final Delegate3 name$delegate = new Delegate3(); @NotNull private final Delegate3 lastname$delegate = new Delegate3(); private int updateCount; @NotNull public final String getName() { return this .name$delegate.getValue(this , $$delegatedProperties[0 ]); } public final void setName(@NotNull String var1) { this .name$delegate.setValue(this , $$delegatedProperties[0 ], var1); } @NotNull public final String getLastname() { return this .lastname$delegate.getValue(this , $$delegatedProperties[1 ]); } public final void setLastname(@NotNull String var1) { this .lastname$delegate.setValue(this , $$delegatedProperties[1 ], var1); } public final int getUpdateCount() { return this .updateCount; } public final void setUpdateCount(int var1) { this .updateCount = var1; } }
可以看到,这和我们在上一节中写的大致是一样的。
thisRef 就是发起委托的对象。
值得注意的是,在 Person6.java 中创建了一个 $$delegatedProperties 对象,它是一个 KProperty 类型的数组,它的元素封装了类,属性名,getter 方法签名等信息,也会传递给委托类,这是用来做什么的?
本例子中我们确实用不到这些信息。KProperty 主要是用来封装属性的元信息,提供给委托类使用,比如在委托类的 setValue 方法中通知属性发生变化时,就会用到 KProperty 里的属性名信息了。
对委托属性约定的再认识 ** **
虽然 Kotlin 提供了 ReadWriteProperty 和 ReadOnlyProperty 封装了约定的方法给我们使用,但是当我们定义委托类时并不是一定要实现 Kotlin 提供的接口。
实际上,只要保持委托类里的 setValue 和 getValue 方法与约定的 setValue 方法和 getValue 方法一致就可以了。
修改委托类为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Delegate4 { var formattedString = "" operator fun getValue (thisRef: Any , property: KProperty <*>) : String { return formattedString + "-" + formattedString.length } operator fun setValue (thisRef: Any , property: KProperty <*>, value: String ) { if (thisRef is Person7) { thisRef.updateCount++ } formattedString = value.lowercase().replaceFirstChar { it.uppercase() } } }
修改 Person 类为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Person7 { var name: String by Delegate4() var lastname: String by Delegate4() var updateCount = 0 }fun main () { val person = Person7() person.name = "peter" person.lastname = "wang" println("name=${person.name} " ) println("lastname=${person.lastname} " ) println("updateCount=${person.updateCount} " ) }
约定方法可以使用扩展函数来实现:
比如,Delegate 类不符合委托属性的约定方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Delegate5 { var formattedString = "" fun get () : String { return formattedString + "-" + formattedString.length } fun set (thisRef: Any , value: String ) { if (thisRef is Person8) { thisRef.updateCount++ } formattedString = value.lowercase().replaceFirstChar { it.uppercase() } } }
这时可以使用扩展函数来实现约定方法,新建 Delegate5Extension.kt:
1 2 3 4 operator fun Delegate5.setValue (thisRef: Any , property: KProperty <*>, value: String ) = set (thisRef, value)operator fun Delegate5.getValue (thisRef: Any , property: KProperty <*>) = get ()
修改 Person 类为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Person8 { var name: String by Delegate5() var lastname: String by Delegate5() var updateCount = 0 }fun main () { val person = Person8() person.name = "peter" person.lastname = "wang" println("name=${person.name} " ) println("lastname=${person.lastname} " ) println("updateCount=${person.updateCount} " ) }
/ Kotlin内置委托 /
lazy() 函数 lazy() 函数用于实现属性的惰性初始化,即只有在第一次访问属性时,才对它进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Person (name: String) { val emails: List<String> by lazy { loadEmailsByName(name) } private fun loadEmailsByName (name: String ) : List<String> { println("loadEmailsByName called" ) return listOf("Email1" , "Email2" , "Email3" ) } }fun main () { val p = Person("Peter" ) println(p.emails) println(p.emails) }
可以看到,两次访问 emails 属性,只有第一次调用了 loadEmailsByName 方法。
如果把访问 emails 属性的代码注释掉,会看到没有任何打印,说明 emails 属性确实是惰性初始化的。
为什么 lazy() 函数可以放在 by 后面用于获取委托对象呢?
这是因为 Lazy.kt 中定义了符合约定的扩展函数:
1 public inline operator fun <T> Lazy<T> .getValue (thisRef: Any ?, property: KProperty <*>) : T = value
Delegates.notNull() Delegates.notNull() 用于实现属性的延迟初始化,和 lateinit 类似。
它们的区别是:
notNull 会给每个属性额外创建一个对象,而 lateinit 不会;
notNull 可以用于基本数据类型的延迟初始化,而 lateinit 不可以。
1 2 3 4 5 6 7 8 9 10 class Person4 { val fullname: String by Delegates.notNull<String>() lateinit var fullname2: String companion object { lateinit var list: MutableList<String> val l : List<String> by Delegates.notNull<List<String>>() } }
/ 最后 /
本文比较详细地介绍了 Kotlin 为什么要出现 by 关键字,以及它的两种用法。通过小的例子一步一步引出 by 关键字的使用。
需要说明的是,本文对于 by 关键字的委托属性的用法的实际应用并没有一一说明,这部分需要查看 Delegated properties-Kotlin官方文档 (https://kotlinlang.org/docs/delegated-properties.html)。
另外,本文没有把委托属性在属性值变化监听的应用例子写在文章里,主要考虑到大家对 PropertyChangeSupport 这个接口比较陌生,不太适合做基本实例说明。不过,这个例子在本文提供的代码中是包含的。需要说明的是,Kotlin 的内置的 Delegates.observable 和 Delegates.vetoable 就是对属性值变化监听或者否决的代码封装。
代码在这里(https://github.com/jhwsx/BlogCodes/tree/master/KotlinByStudy)。
文章的参考部分是不可多得的学习资料。大家也可以看看。
/ 参考 /
Delegation-Kotlin官方文档
https://kotlinlang.org/docs/delegation.html
Delegated properties-Kotlin官方文档
https://kotlinlang.org/docs/delegated-properties.html
Delegating Delegates to Kotlin-AndroidDevelopersBlog(出自谷歌技术推广工程师,使用的例子比较贴切)
https://medium.com/androiddevelopers/delegating-delegates-to-kotlin-ee0a0b21c52b
Built-in Delegates-AndroidDevelopersBlog*(出自谷歌技术推广工程师,使用的例子比较贴切)*
https://medium.com/androiddevelopers/built-in-delegates-4811947e781f
Built-in Delegates-MAD Skills*(出自谷歌技术推广工程师,这个是视频)*
https://www.youtube.com/watch?v=s5qn5QhFntY&list=PLWz5rJ2EKKc_T0fSZc9obnmnWcjvmJdw_&index=3
[译]带你揭开Kotlin中属性代理和懒加载语法糖衣(对 lazy 进行的详细地说明)
https://zhuanlan.zhihu.com/p/65914552
Kotlin “By” Class Delegation: Favor Composition Over Inheritance(作者详细地说明了类委托,介绍了组合优于继承,并举出 Android 中的 AppCompatDelegate 的例子,Guava 开源库中集合中使用组合代替继承的例子。)
https://medium.com/rocket-fuel/kotlin-by-class-delegation-favor-composition-over-inheritance-a1b97fecd839
Kotlin ‘By’ Property Delegation: Create Reusable Code
https://medium.com/rocket-fuel/kotlin-by-property-delegation-create-reusable-code-f2bc2253e227
1.类委托 (1)概念 本类需要实现的方法/属性 ,借用其他已实现该方法/属性 的对象作为自己的实现;
一旦使用了某类作为委托类,该类就能借用该委托类实现的方法/属性。
(2)定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface Creature { fun run () val type: String }class DelegateClass : Creature { override val type: String get () = "委托类该属性" override fun run () { println("代理类执行run方法" ) } }
①委托类作为构造器形参传入(常用) 1 2 3 4 class Human (delegateClass: DelegateClass) : Creature by delegateClass
②新建委托类对象 1 2 3 4 class Dog : Creature by DelegateClass ()
③新建委托类对象,并自己实现方法/属性 1 2 3 4 5 6 7 8 9 10 11 class Pig : Creature by DelegateClass () { override fun run () { println("自己执行实现的run方法" ) } override val type: String get () = "自己实现的type属性" }
调用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 fun main (args: Array <String >) { val delegateClass = DelegateClass() val human = Human(delegateClass) human.run() println(human.type) val human2 = Human(delegateClass) human2.run() println(human2.type) val dog = Dog() dog.run() println(dog.type) val pig = Pig() pig.run() println(pig.type) }
2.属性委托 (1)概念 多个类的类似属性交给委托类统一实现,避免每个类都要单独重复实现一次。
(2)定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class Jobs { var name: String = "工作名" operator fun setValue (thisRef: SoftWareDev , property: KProperty <*>, value: String ) { this .name = value } operator fun getValue (thisRef: SoftWareDev , property: KProperty <*>) : String { return this .name } }interface SoftWareDev class AndroidDev : SoftWareDev { var name: String by Jobs() }class PhpDev : SoftWareDev { var name: String by Jobs() }class ExtensionClass : SoftWareDev val ExtensionClass.name by Jobs()fun main (args: Array <String >) { val androidDev = AndroidDev() androidDev.name = "android开发" println(androidDev.name) val phpDev = PhpDev() println(phpDev.name) println(ExtensionClass().name) }
(3)关于委托类的委托属性的getValue、setValue形参说明 ①getValue(thisRef, property)
thisRef:代表委托属性所属对象,因此thisDef类型需要与委托属性所属对象类型一致 ,或者是其父类 ,如上面代码thisRef类型为SoftWareDev,是AndroidDev和PhpDev两个类的父类类型 ;
property:代表目标属性,该属性必须是KProperty<*>类型 或其父类类型 ;
返回值:返回目标属性相同的类型 ,或者其子类类型 。
②setValue(thisRef, property, value)
thisRef:同上;
property:同上;
value:为目标属性设置的新的值,因此该值类型必须与目标属性类型一致 ,或者是其父类 。
总结:传入形参类型需与目标类型一致或者是其父类类型,返回值类型需与目标类型一致或者是其子类类型。
(4)关于ReadOnlyProperty和ReadWriteProperty接口
ReadOnlyProperty接口定义了getValue方法;
ReadWriteProperty接口定义了getValue、setValue方法。
因此,委托类可以实现ReadOnlyProperty(val声明待委托属性时)或者ReadWriteProperty(var声明待委托属性时)接口来进行委托属性的定义。
上面Jobs类完全可以这么实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Jobs : ReadWriteProperty <SoftWareDev,String > { var name: String = "工作名" override operator fun setValue (thisRef: SoftWareDev , property: KProperty <*>, value: String ) { this .name = value } override operator fun getValue (thisRef: SoftWareDev , property: KProperty <*>) : String { return this .name } } 复制 全