kotlin by 关键字

本篇文章来自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 {
// 因为 super.addAll 内部调用了 add 方法,所以这里不必统计添加个数了。
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")
}
/*
打印如下:
4 objects were added, 3 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")
}
/*
打印如下:
4 objects were added, 3 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")
}
/*
打印如下:
4 objects were added, 3 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;
}

// $FF: synthetic method
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();
}

// $FF: bridge method
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}")
}
/*
打印日志:
name=Peter-5
lastname=Wang-4
updateCount=2
*/

ps:这里面使用 Kotlin 中的支持字段 field ,需要学习可以查看笔者的这篇文章:Kotlin 的 Backing Fields 和 Backing Propertieshttps://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}")
}
/*
name=Peter-5
lastname=Wang-4
updateCount=2
*/

查看日志打印,这样做可以实现需求。

使用类来封装重复代码

现在需求又来了,有一个 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
}
/*
打印日志:
name=Peter-5
lastname=Wang-4
updateCount=2
*/

到这里,需要强调的是,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}")
}
/*
打印如下:
name=Peter-5
lastname=Wang-4
updateCount=2
*/

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();
}

// $FF: synthetic method
// $FF: bridge method
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;
}

// $FF: synthetic method
// $FF: bridge method
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 {
// $FF: synthetic field
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 不可以省略
operator fun getValue(thisRef: Any, property: KProperty<*>): String {
return formattedString + "-" + formattedString.length
}
// 注意这里的 operator 不可以省略
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}")
}
/*
打印日志:
name=Peter-5
lastname=Wang-4
updateCount=2
*/

/ 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)
}
/*
打印日志:
loadEmailsByName called
[Email1, Email2, Email3]
[Email1, Email2, Email3]
*/

可以看到,两次访问 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
// lateinit var age: Int

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
/**
* 定义一个生物接口,有一个run抽象方法和一个抽象属性
*/
interface Creature {
fun run()
val type: String
}

/**
* 定义一个委托类,实现了Creature接口
*/
class DelegateClass : Creature {
override val type: String
get() = "委托类该属性"

override fun run() {
println("代理类执行run方法")
}
}

①委托类作为构造器形参传入(常用)

1
2
3
4
/**
* 定义一个Human类,实现了Creature接口,委托类作为形参传入,由形参委托类对象作为委托对象
*/
class Human(delegateClass: DelegateClass) : Creature by delegateClass

②新建委托类对象

1
2
3
4
/**
* 定义一个Dog类,实现了Creature接口,新建一个委托类作为委托对象
*/
class Dog : Creature by DelegateClass()

③新建委托类对象,并自己实现方法/属性

1
2
3
4
5
6
7
8
9
10
11
/**
* 定义一个pig类,实现了Creature接口,新建一个委托类作为委托对象,并自己实现了抽象方法
*/
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()//结果:代理类执行run方法
println(human.type)//结果:委托类该属性
val human2 = Human(delegateClass)
human2.run()//结果:代理类执行run方法
println(human2.type)//结果:委托类该属性

//新建对象
val dog = Dog()
dog.run()//结果:代理类执行run方法
println(dog.type)//结果:委托类该属性

//新建对象并自己实现方法/属性
val pig = Pig()
pig.run()//结果:自己执行实现的run方法
println(pig.type)//结果:自己实现的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
/**
* 定义一个委托类Jobs,
* 为属性name创建了operator修饰的getValue和setValue方法
* 该属性可被其他了委托该类共享
*/
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

/**
* 定义一个AndroidDev类,实现SoftWareDev接口
* 属性name委托Jobs类实现
*/
class AndroidDev : SoftWareDev {
var name: String by Jobs()
}

/**
* 定义一个PhpDev类,实现了SoftWareDev接口
* 属性name委托Jobs类实现
*/
class PhpDev : SoftWareDev {
var name: String by Jobs()
}

/**
* 定义一个类实现了SoftWareDev接口
*/
class ExtensionClass : SoftWareDev

/**
* 为ExtensionClass扩展属性,该属性由委托类Jobs实现
*/
val ExtensionClass.name by Jobs()

fun main(args: Array<String>) {
val androidDev = AndroidDev()
androidDev.name = "android开发"
println(androidDev.name)//结果:android开发
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
/**
* 定义一个委托类Jobs,
* 实现了ReadWriteProperty接口,重写setValue、getValue方法
* 该属性可被其他了委托该类共享
*/
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
}
}
复制 全

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!