7.11 data 数据类
7.11.1 构造函数中的 val/var
在开始讲数据类之前,我们先来看一下几种类声明的写法。
写法一:
class Aook(name: String)
这样写,这个name变量是无法被外部访问到的。它对应的反编译之后的Java代码如下:
public final class Aook {
public Aook(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
}
}
写法二:
要想这个name变量被访问到,我们可以在类体中再声明一个变量,然后把这个构造函数中的参数赋值给它:
class Cook(name: String) {
val name = name
}
测试代码:
val cook = Cook("Cook")
cook.name
对应的Java实现代码是:
public final class Cook {
@NotNull
private final String name;
@NotNull
public final String getName() {
return this.name;
}
public Cook(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
}
}
写法三:
class Dook(val name: String)
class Eook(var name: String)
构造函数中带var、val修饰的变量,Kotlin编译器会自动为它们生成getter、setter函数。
上面的写法对应的Java代码就是:
public final class Dook {
@NotNull
private final String name;
@NotNull
public final String getName() {
return this.name;
}
public Dook(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
}
}
public final class Eook {
@NotNull
private String name;
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public Eook(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
}
}
测试代码:
val dook = Dook("Dook")
dook.name
val eook = Eook("Eook")
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 中,也有对应这样的领域实体类的概念,并在语言层面上做了支持,叫做数据类 :
data class Book(val name: String)
data class Fook(var name: String)
data class User(val name: String, val gender: String, val age: Int) {
fun validate(): Boolean {
return true
}
}
这里的var/val是必须要带上的。因为编译器要把主构造函数中声明的所有属性,自动生成以下函数:
equals()/hashCode()
toString() : 格式是 User(name=Jacky, gender=Male, age=10)
componentN() 函数 : 按声明顺序对应于所有属性component1()、component2() ...
copy() 函数
如果我们自定义了这些函数,或者继承父类重写了这些函数,编译器就不会再去生成。
测试代码:
val book = Book("Book")
book.name
book.copy("Book2")
val jack = User("Jack", "Male", 1)
jack.name
jack.gender
jack.age
jack.toString()
jack.validate()
val olderJack = jack.copy(age = 2)
val anotherJack = jack.copy(name = "Jacky", age = 10)
在一些场景下,我们需要复制一个对象来改变它的部分属性,而其余部分保持不变。 copy() 函数就是为此而生成。例如上面的的 User 类的copy函数的使用:
val olderJack = jack.copy(age = 2)
val anotherJack = jack.copy(name = "Jacky", age = 10)
7.11.4 数据类的限制
数据类有以下的限制要求:
1.主构造函数需要至少有一个参数。下面的写法是错误的:
data class Gook // error, data class must have at least one primary constructor parameter
2.主构造函数的所有参数需要标记为 val 或 var;
data class Hook(name: String)// error, data class must have only var/val property
跟普通类一样,数据类也可以有次级构造函数:
data class LoginUser(val name: String = "", val password: String = "") : DBase(), IBaseA, IBaseB {
var isActive = true
constructor(name: String, password: String, isActive: Boolean) : this(name, password) {
this.isActive = isActive
}
...
}
3.数据类不能是抽象、开放、密封或者内部的。也就是说,下面的写法都是错误的:
abstract data class Iook(val name: String) // modifier abstract is incompatible with data
open data class Jook(val name: String) // modifier abstract is incompatible with data
sealed data class Kook(val name: String)// modifier sealed is incompatible with data
inner data class Look(val name: String)// modifier inner is incompatible with data
数据类只能是final的:
final data class Mook(val name: String) // modifier abstract is incompatible with data
4.在1.1之前数据类只能实现接口。自 1.1 起,数据类可以扩展其他类。代码示例:
open class DBase
interface IBaseA
interface IBaseB
data class LoginUser(val name: String, val password: String) : DBase(), IBaseA, IBaseB {
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
override fun hashCode(): Int {
return super.hashCode()
}
override fun toString(): String {
return super.toString()
}
fun validate(): Boolean {
return true
}
}
测试代码:
val loginUser1 = LoginUser("Admin", "admin")
println(loginUser1.component1())
println(loginUser1.component2())
println(loginUser1.name)
println(loginUser1.password)
println(loginUser1.toString())
输出:
Admin
admin
Admin
admin
com.easy.kotlin.LoginUser@7440e464
可以看出,由于我们重写了override fun toString(): String
, 对应的输出使我们熟悉的类的输出格式。
如果我们不重写这个toString函数,则会默认输出:
LoginUser(name=Admin, password=admin)
上面的类声明的构造函数,要求我们每次必须初始化name、password的值,如果我们想拥有一个无参的构造函数,我们只要对所有的属性指定默认值即可:
data class LoginUser(val name: String = "", val password: String = "") : DBase(), IBaseA, IBaseB {
...
}
这样我们在创建对象的时候,就可以直接使用:
val loginUser3 = LoginUser()
loginUser3.name
loginUser3.password
7.11.5 数据类的解构
解构相当于 Component 函数的逆向映射:
val helen = User("Helen", "Female", 15)
val (name, gender, age) = helen
println("$name, $gender, $age years of age")
输出:Helen, Female, 15 years of age
7.11.6 标准数据类Pair
和Triple
标准库中的二元组 Pair类就是一个数据类:
public data class Pair<out A, out B>(
public val first: A,
public val second: B) : Serializable {
public override fun toString(): String = "($first, $second)"
}
Kotlin标准库中,对Pair类还增加了转换成List的扩展函数:
public fun <T> Pair<T, T>.toList(): List<T> = listOf(first, second)
还有三元组Triple类:
public data class Triple<out A, out B, out C>(
public val first: A,
public val second: B,
public val third: C) : Serializable {
public override fun toString(): String = "($first, $second, $third)"
}
fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)