7.11 data 数据类
7.11.1 构造函数中的 val/var
在开始讲数据类之前,我们先来看一下几种类声明的写法。
写法一:
1. class Aook(name: String)
这样写,这个name变量是无法被外部访问到的。它对应的反编译之后的Java代码如下:
1. public final class Aook {
2. public Aook(@NotNull String name) {
3. Intrinsics.checkParameterIsNotNull(name, "name");
4. super();
5. }
6. }
写法二: 要想这个name变量被访问到,我们可以在类体中再声明一个变量,然后把这个构造函数中的参数赋值给它:
1. class Cook(name: String) {
2. val name = name
3. }
测试代码:
1. val cook = Cook("Cook")
2. cook.name
对应的Java实现代码是:
1. public final class Cook {
2. @NotNull
3. private final String name;
5. @NotNull
6. public final String getName() {
7. return this.name;
8. }
10. public Cook(@NotNull String name) {
11. Intrinsics.checkParameterIsNotNull(name, "name");
12. super();
13. this.name = name;
14. }
15. }
写法三:
1. class Dook(val name: String)
2. class Eook(var name: String)
构造函数中带var、val修饰的变量,Kotlin编译器会自动为它们生成getter、setter函数。
上面的写法对应的Java代码就是:
1. public final class Dook {
2. @NotNull
3. private final String name;
5. @NotNull
6. public final String getName() {
7. return this.name;
8. }
10. public Dook(@NotNull String name) {
11. Intrinsics.checkParameterIsNotNull(name, "name");
12. super();
13. this.name = name;
14. }
15. }
17. public final class Eook {
18. @NotNull
19. private String name;
21. @NotNull
22. public final String getName() {
23. return this.name;
24. }
26. public final void setName(@NotNull String var1) {
27. Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
28. this.name = var1;
29. }
31. public Eook(@NotNull String name) {
32. Intrinsics.checkParameterIsNotNull(name, "name");
33. super();
34. this.name = name;
35. }
36. }
测试代码:
1. val dook = Dook("Dook")
2. dook.name
3. val eook = Eook("Eook")
4. eook.name
下面我们来学习一下Kotlin中的数据类: data class 。
7.11.2 领域实体类
我们写Java代码的时候,会经常创建一些只保存数据的类。比如说:
- POJO类:POJO全称是Plain Ordinary Java Object / Pure Old Java Object,中文可以翻译成:普通Java类,具有一部分getter/setter方法的那种类就可以称作POJO。
- DTO类:Data Transfer Object,数据传输对象类,泛指用于展示层与服务层之间的数据传输对象。
- VO类:VO有两种说法,一个是ViewObject,一个是ValueObject。
- PO类:Persisent Object,持久对象。它们是由一组属性和属性的get和set方法组成。PO是在持久层所使用,用来封装原始数据。
- BO类:Business Object,业务对象层,表示应用程序领域内“事物”的所有实体类。
- DO类:Domain Object,领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。
等等。
这些我们统称为领域模型中的实体类。最简单的实体类是POJO类,含有属性及属性对应的set和get方法,实体类常见的方法还有用于输出自身数据的toString方法。
7.11.3 数据类 data class 的概念
在 Kotlin 中,也有对应这样的领域实体类的概念,并在语言层面上做了支持,叫做数据类 :
1. data class Book(val name: String)
2. data class Fook(var name: String)
3. data class User(val name: String, val gender: String, val age: Int) {
4. fun validate(): Boolean {
5. return true
6. }
7. }
这里的var/val是必须要带上的。因为编译器要把主构造函数中声明的所有属性,自动生成以下函数:
1. equals()/hashCode()
2. toString() : 格式是 User(name=Jacky, gender=Male, age=10)
3. componentN() 函数 : 按声明顺序对应于所有属性component1()、component2() ...
4. copy() 函数
如果我们自定义了这些函数,或者继承父类重写了这些函数,编译器就不会再去生成。
测试代码:
1. val book = Book("Book")
2. book.name
3. book.copy("Book2")
5. val jack = User("Jack", "Male", 1)
6. jack.name
7. jack.gender
8. jack.age
9. jack.toString()
10. jack.validate()
12. val olderJack = jack.copy(age = 2)
13. val anotherJack = jack.copy(name = "Jacky", age = 10)
在一些场景下,我们需要复制一个对象来改变它的部分属性,而其余部分保持不变。 copy() 函数就是为此而生成。例如上面的的 User 类的copy函数的使用:
1. val olderJack = jack.copy(age = 2)
2. val anotherJack = jack.copy(name = "Jacky", age = 10)
7.11.4 数据类的限制
数据类有以下的限制要求:
1.主构造函数需要至少有一个参数。下面的写法是错误的:
1. data class Gook // error, data class must have at least one primary constructor parameter
2.主构造函数的所有参数需要标记为 val 或 var;
1. data class Hook(name: String)// error, data class must have only var/val property
跟普通类一样,数据类也可以有次级构造函数:
1. data class LoginUser(val name: String = "", val password: String = "") : DBase(), IBaseA, IBaseB {
3. var isActive = true
5. constructor(name: String, password: String, isActive: Boolean) : this(name, password) {
6. this.isActive = isActive
7. }
8. ...
9. }
3.数据类不能是抽象、开放、密封或者内部的。也就是说,下面的写法都是错误的:
1. abstract data class Iook(val name: String) // modifier abstract is incompatible with data
2. open data class Jook(val name: String) // modifier abstract is incompatible with data
3. sealed data class Kook(val name: String)// modifier sealed is incompatible with data
4. inner data class Look(val name: String)// modifier inner is incompatible with data
数据类只能是final的:
1. final data class Mook(val name: String) // modifier abstract is incompatible with data
4.在1.1之前数据类只能实现接口。自 1.1 起,数据类可以扩展其他类。代码示例:
1. open class DBase
2. interface IBaseA
3. interface IBaseB
5. data class LoginUser(val name: String, val password: String) : DBase(), IBaseA, IBaseB {
7. override fun equals(other: Any?): Boolean {
8. return super.equals(other)
9. }
11. override fun hashCode(): Int {
12. return super.hashCode()
13. }
15. override fun toString(): String {
16. return super.toString()
17. }
19. fun validate(): Boolean {
20. return true
21. }
22. }
测试代码:
1. val loginUser1 = LoginUser("Admin", "admin")
2. println(loginUser1.component1())
3. println(loginUser1.component2())
4. println(loginUser1.name)
5. println(loginUser1.password)
6. println(loginUser1.toString())
输出:
1. Admin
2. admin
3. Admin
4. admin
5. com.easy.kotlin.LoginUser@7440e464
可以看出,由于我们重写了override fun toString(): String, 对应的输出使我们熟悉的类的输出格式。
如果我们不重写这个toString函数,则会默认输出:
1. LoginUser(name=Admin, password=admin)
上面的类声明的构造函数,要求我们每次必须初始化name、password的值,如果我们想拥有一个无参的构造函数,我们只要对所有的属性指定默认值即可:
1. data class LoginUser(val name: String = "", val password: String = "") : DBase(), IBaseA, IBaseB {
2. ...
3. }
这样我们在创建对象的时候,就可以直接使用:
1. val loginUser3 = LoginUser()
2. loginUser3.name
3. loginUser3.password
7.11.5 数据类的解构
解构相当于 Component 函数的逆向映射:
1. val helen = User("Helen", "Female", 15)
2. val (name, gender, age) = helen
3. println("$name, $gender, $age years of age")
输出:Helen, Female, 15 years of age
7.11.6 标准数据类Pair 和Triple
标准库中的二元组 Pair类就是一个数据类:
1. public data class Pair<out A, out B>(
2. public val first: A,
3. public val second: B) : Serializable {
4. public override fun toString(): String = "($first, $second)"
5. }
Kotlin标准库中,对Pair类还增加了转换成List的扩展函数:
1. public fun <T> Pair<T, T>.toList(): List<T> = listOf(first, second)
还有三元组Triple类:
1. public data class Triple<out A, out B, out C>(
2. public val first: A,
3. public val second: B,
4. public val third: C) : Serializable {
5. public override fun toString(): String = "($first, $second, $third)"
6. }
7. fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)
