数据类是 Kotlin 的一个语法糖。 Kotlin 编译器会自动为数据类生成一些成员函数,以提高开发效率
如果在程序中使用数据库,或映射 JSON 数据,很可能会将查询结果集或 JSON 格式的数 据映射成 为对象,或将对象映射成为数据集或 JSON 格式的数据。无论是哪一种,我们总是需 要一个类来表示数据。例如,要表示用户的数据,需要建立一个 User 类,并通过构造器传入 两个参数值: name和 age。
class User(name : String,age : Int)
{
var name : String = name
var age : Int = age
}
在User类中,主构造器有两个参数: name和age,在类中还定义了两个 属性: name和 age,并用主构造器的两个参数分别初始化了两个成员属性。
对于表示数据的类来说,除了要保存数据外,还要进行一些操作, 如输出对象的值,比较对象是否 相等 。 Kotlin 代码前者会调用对象的 toString 函数,后者会调用对象的 equals 函数。
/**
* 输出user1 和user2 时,调用了这两个对象的toString函数,
* 而toString函数默认会调用hashcode函数输出当前对象的hashcode,
* 并在前面加上"类名@",就像User@
*/
var user1 : User = User("mike",21)
var user2 : User = User("mike",21)
println(user1)
println(user2)
/**
* 对象的 equals方法默认比较了对象之间的 hashcode,如果相同,就是一个对象,它们也就 相等了 。
* 不过,在实际应用中,需要 toString 函数输出对象属性值,
* 而 equals 函数通过属性值 是否相等进行比较。
* 只有所有的属性值都相等,两个对象才会判定相等。
*/
println(user1.equals(user2))
为了使用 toString 函数输出 name 和 age 属性值,使用 equals 函数比较两个 User对象是否 相等,需要在 User类中重写这两个函数,代码如下:
class User(name : String,age : Int)
{
var name : String = name
var age : Int = age
override fun toString(): String {
return "User (name = ${name},age = ${age})"
}
override fun equals(other: Any?): Boolean {
// 判断other是否为User对象, 如果不是User对象,直接返回false
if(other is User)
{
//kt会自动转换数据类型, 根据is来判断,other现在已经是User对象了
if(name == other.name && age == other.age)
{
return true
}
}else
{
return false
}
return false
}
}
User 类尽管可 以完全满足我们的要求,但如果每个表示数据 的类都要重写 toString和 equals方法,而且都要加属性,岂不是很麻烦,为了让开发效率更进 一步提升 , Kotlin 加入了数据类。所谓数据类,其实就是只定义必要的部分,其 余的部分 ,可以自动推导。
从 User类的代码可以看出,只有 name 和 age 是必要的,其余的都可以自动推导,因此, 只需要在 User类中指定 name 和 age 即可。如何指定 name 和 age 呢?数据类规定,属性要通 过主构造器指定,而且数据类要在 class关键宇前面加 data。因此,如果前面的 User类用数据 类的写法 ,就变成了下面的样子。
data class User(val name : String,var age : Int)
User数据类中并没有类的实现体,实际上,数据类和普通类完全一样, 也可以拥有类的实现体, 数据类和普通类最大不同,就是数据类可以根据主构造器的参数自动生成相关的代码,因此, 如果一个 Kotlin类,同时兼有普通类,以及存储和管理数据的功能,建议直接使用数据类。
open class MyClass
{
}
data class User(val name : String,var age : Int):MyClass()
{
fun process()
{
println("process")
}
}
编写 一个数据类需要注意的事项如下 。
为主构造器的每一个参数添加一个默认值
data class User(val name : String = "Bill",var age : Int = 20)
添加一个次构造器
data class User(val name : String = "Bill",var age : Int = 20):MyClass()
{
//添加一个构造器 次构造器
constructor():this("Bill",20)
{
}
fun process()
{
println("process")
}
}
在实际应用中 , 经常需要复制一个对象,然后修改它的一部分属性, 这就需要数据类提供 一种机制可以复制对象。在前面讲过, Kotlin编译器会为数据类 自动生成一些代码 (bytecode), 这些代码不仅包括 toS仕ing和 equals,还包括一个 copy 函数,该函数的作用就是复制数据类的实例。
copy 函数的代码 ,核心代码就是下面的样子 。 很明显,在 copy 函数中,就是创建了一个当前数据类的实例, 并传入了 name和 age属性
fun copy(name: String = this.name,age : Int = this.age) = User(name,age)
使用数据类可直接调用copy()函数
现在先来解释一下什么叫数据类成员的解构 ,这里的关键是解构 ,也就是解除结构 。在数 据类中,用属性表示数据, 这些属性属于同一个数据类。要想使用这些属性,必须要首先引用 数据类对象。这里的解构就是指将这些数据对象中 的属性提取出来,分别赋给单个的变量 ,这 样就可以单独使用它们了
val jane = MyUser(name = "Jane",age = 20)
//将MyUser.name MyUser.age 分别赋予name和age变量
val (name,age) = jane
println("$name , $age year of age")
封闭类也是 Kotlin 的一个语法糖。可以把它理解为枚举的扩展。一个封闭类,前面用 sealed关键字标识。可以有任意多个子类或对象。封 闭类的值只能是这些子类或对象 。使用封闭类的 好处主要是 when 表达式,不需要再使用 else 形式了。下面给出一个例子。
sealed class Expr
data class Const(val number : Double) : Expr()
data class Sum(val param1 : Expr,val param2 : Expr) : Expr
object NotANumber : Expr()
fun eval (expr: Expr) : Double = when(expr)
{
is Const -> expr.number
is Sum -> eval(expr.param1) + eval(expr.param2)
NotANumber -> Double.NaN
//在这里不要求使用else子句匹配其他情况
}
在这段代码中 , Expr 是一个封闭类,该类有两个数据类( Const 和 Sum)和一个对象 CNotANumber)。下面的函数判断 Expr 类型参数 的种类,这里面只有 3 个种类: Const、 Sum 和 NotANumber