Kotlin系列之数据类和类委托

今天聊聊Kotlin中的数据类和委托类。

在Java中是没有数据类和类委托的概念的。但是简单一点说数据类就是我们常说的Java中的Bean,它只是单纯为了表示数据而存在,就是一个方便的数据容器,而类委托表面看来就是主要实现将一个类委托给另一个类,具体内容我们往下看。

Kotlin中的数据类

上一节的代码中我们讨论了Kotlin中的通用对象方法,我们演示用的类就可以看作是一个数据类。代码如下

Kotlin代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
open class User(val name: String, private val age: Int){
override fun toString() = "User{name='$name', age=$age}"

override fun equals(other: Any?): Boolean {
if (other == this){
return true;
}
if (other == null || other !is User){
return false;
}
return name == other.name && age == other.age
}

override fun hashCode(): Int {
var result = name.hashCode() ?: 0
result = 31 * result + age
return result
}
}

不知道大家有没有觉得重写这些方法也是有套路的,其实在IDEA等IDE中都有一键生成这种模板代码的功能,所以我们根本不需要写。其实不止如此,在Kotlin中还有更简单的方法。
具体做法就是在你的数据类前面加一个data关键字,Kotlin的编译器就会在背后自动帮你生成这些代码。就像下面这样。
Kotlin代码

1
data class User(val name: String, private val age: Int)

是不是超级简洁,对于编译器自动生成的方法。
equals()用来比较实例
hashCode()用来生成作为类似于HashMap这种基于哈希的容器的键
toString()用来生成按声明顺序排列的所有属性字段的字符串表达形式
这里需要注意的一点是,equals()方法比较的字段是在主构造方法中声明的属性,hashCode()方法也是根据主构造方法中声明的属性来计算哈希值。没有在主构造方法中声明的属性不会加入到相等性检查和哈希值计算中去。

除了上面的三个方法,Kotlin编译器还为我们多生成了一个copy()方法。那么为什么会有这个方法呢?它是用来做什么的呢?
先说一下,我们在声明一个数据类时,为了实现对象的不可变性,一般要求我们将主构造方法主的属性都声明为val的,这样就可以保证数据类的实例在生成以后是不可变的和安全的,这在HashMap这种要求key不可变的容器中是非常重要的。同时在多线程情况下,对象会一直保持初始状态,也是安全的。

那么copy()方法是用来做什么的呢?该方法用来创建一个实例的副本,并且可以在创建时修改该副本实例的某些属性,就像下面这样。

1
2
3
4
5
6
7
8
9
10
data class User(val name: String, val age: Int)

fun main(atgs: Array<String>){
val user1 = User("遇见", 12)

val user2 = user1.copy(name = "雨", age = 21)

println(user1)
println(user2)
}

输出结果

1
2
User(name=遇见, age=12)
User(name=雨, age=21)

Kotlin中的类委托

类委托,表面意思就是将一个类委托给另一个类,那它的应用场景是什么呢?我们下面举一个例子。
我们扩展一个集合类,统计我们一共向集合中添加了多少次元素,也就是我们做了多少次添加操作,这里我们先使用Java来实现一下。

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
public class CountableSet<T> implements Collection<T> {

public int count = 0;
private Set<T> mList = new HashSet<T>();

@Override
public int size() {
return mList.size();
}

@Override
public boolean isEmpty() {
return mList.isEmpty();
}

@Override
public boolean contains(Object o) {
return mList.contains(o);
}

@NotNull
@Override
public Iterator<T> iterator() {
return mList.iterator();
}

@NotNull
@Override
public Object[] toArray() {
return mList.toArray();
}

@NotNull
@Override
public <T1> T1[] toArray(@NotNull T1[] a) {
return mList.toArray(a);
}

@Override
public boolean add(T t) {
count++;
return mList.add(t);
}

@Override
public boolean remove(Object o) {
return mList.remove(o);
}

@Override
public boolean containsAll(@NotNull Collection<?> c) {
return mList.containsAll(c);
}

@Override
public boolean addAll(@NotNull Collection<? extends T> c) {
count += c.size();
return mList.addAll(c);
}

@Override
public boolean removeAll(@NotNull Collection<?> c) {
return mList.removeAll(c);
}

@Override
public boolean retainAll(@NotNull Collection<?> c) {
return mList.retainAll(c);
}

@Override
public void clear() {
mList.clear();
}
}

我们的做法是实现了一个集合类,在内部维护一个HashSet,在add()和addAll()方法中做添加次数统计,其他函数的实现我们都委托给HashSet的实例来完成即可。
可以看出上面的大部分代码都是很冗余的,我们为了添加一个小功能,就必须得添加一些多余的委托代码,这是非常不划算的。那么针对这种情况,Kotlin做了类委托,帮我们来自动完成。我们看看Kotlin中该怎么实现上面的功能呢。

Kotlin代码

1
2
3
4
5
6
7
8
9
10
11
12
13
class CountableSet<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by innerSet{
var count = 0

override fun add(element: T): Boolean {
count++
return innerSet.add(element)
}

override fun addAll(elements: Collection<T>): Boolean {
count += elements.size
return innerSet.addAll(elements)
}
}

上面的代码先是在主构造方法中声明了一个innerSet实例,然后使用by关键字表示将所有函数操做都委托给该实例,同时,对于需要变化的函数,我们进行重写实现我们的具体逻辑即可,是不是代码量大大减少了,逻辑也清晰了很多。

写在最后

通过上面的例子,我们又一次感受到了Kotlin的简洁,它可以在保证功能的基础上大大减少代码量,使代码更简洁,逻辑更加清楚。

如果博客对您有帮助,不妨请我喝杯咖啡...