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;
  4. @NotNull
  5. public final String getName() {
  6. return this.name;
  7. }
  8. public Cook(@NotNull String name) {
  9. Intrinsics.checkParameterIsNotNull(name, "name");
  10. super();
  11. this.name = name;
  12. }
  13. }

写法三:

  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;
  4. @NotNull
  5. public final String getName() {
  6. return this.name;
  7. }
  8. public Dook(@NotNull String name) {
  9. Intrinsics.checkParameterIsNotNull(name, "name");
  10. super();
  11. this.name = name;
  12. }
  13. }
  14. public final class Eook {
  15. @NotNull
  16. private String name;
  17. @NotNull
  18. public final String getName() {
  19. return this.name;
  20. }
  21. public final void setName(@NotNull String var1) {
  22. Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
  23. this.name = var1;
  24. }
  25. public Eook(@NotNull String name) {
  26. Intrinsics.checkParameterIsNotNull(name, "name");
  27. super();
  28. this.name = name;
  29. }
  30. }

测试代码:

  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")
  4. val jack = User("Jack", "Male", 1)
  5. jack.name
  6. jack.gender
  7. jack.age
  8. jack.toString()
  9. jack.validate()
  10. val olderJack = jack.copy(age = 2)
  11. 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 {
  2. var isActive = true
  3. constructor(name: String, password: String, isActive: Boolean) : this(name, password) {
  4. this.isActive = isActive
  5. }
  6. ...
  7. }

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
  4. data class LoginUser(val name: String, val password: String) : DBase(), IBaseA, IBaseB {
  5. override fun equals(other: Any?): Boolean {
  6. return super.equals(other)
  7. }
  8. override fun hashCode(): Int {
  9. return super.hashCode()
  10. }
  11. override fun toString(): String {
  12. return super.toString()
  13. }
  14. fun validate(): Boolean {
  15. return true
  16. }
  17. }

测试代码:

  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 标准数据类PairTriple

标准库中的二元组 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)