前言
kotlin官网 (中文版)和kotlin教程学习教程的笔记。
一、数据类
- 数据类是仅仅包含状态而没有任何可执行的操作,通过data关键字标记:
data class User(val name:String,val age:Int)
然后,编译器会根据主构造器中声明的全部属性,自动推断产生以下成员函数:
- equals()/hashCode()函数对
- toString()函数,输出格式为 User(name=**,age=**)
- componentN()函数群,这些函数与类的属性对应, 函数名中的数字 1 到 N, 与属性的声明顺序一致(详细见下文)
- copy()函数(详细见下文)
如果上述任意一个成员函数在类定义体中有明确的定义, 或者从基类继承得到, 那么这个成员函数不会自动生成.
- 为了保证自动生成的代码的行为一致, 并且有意义, 数据类必须满足以下所有要求:
- 主构造器至少要有一个参数
- 主构造器的所有参数必须标记为val或var
- 数据类不能是抽象类、open类、封闭类、内部类
- 数据类不能继承自任何其他类(但可以实现接口)
在 JVM 上, 如果自动生成的类需要拥有一个无参数的构造器, 那么需要为所有的属性指定默认值
data class User(val name: String = "", val age: Int = 0)
二、对象复制
我们经常会需要复制一个对象,然后修改它的一部分属性,但保持其他属性不变,这就是自动生成的copy()函数所需要实现的功能。
对于前面示例中的 User 类, 自动生成的 copy() 函数的实现将会是下面这
样:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
copy()函数的使用方式
data class User(var name:String="jack",val age: Int)
var jack = User(age = 18)
var oldJack = jack.copy(age = 81)
三、数据类中成员数据的解构
编译器会为数据类生成组件函数(Component function),有了这些函数,就可以在解构声明中使用数据类
var jack = User(age = 18)
val(name,age)=jack
println("$name,$age")//输出jack,18
四、标准库中的数据类
Kotlin 的标准库提供了 Pair 和 Triple 类可供使用.
val (rank, money) = Pair(1, "10000")
println(" $rank -> $money")//输出 1 -> 10000
但是, 大多数情况下, 使用有具体名称的数据了是一种更好的设计方式, 因为, 数据类可以为属性指定有含义的名称, 因此可以增加代码的可读性.
小知识补充
解构声明(Destructuring Declaration)
- 将一个对象解构为多个变量,例如val(name,age)=user 这种语法成为解构声明。
- 我们已知一个解构声明回一次性创建多个变量,我们声明了两个变量name和age,并可以单独使用这两个变量println(age) 、 println(name)。
- 解构声明在编译时被分解为以下代码
val name=user.component1()
val age=user.component2()
当然, 还可以存在component3() 和 component4() 等等.
- 任何东西都可以作为解构声明右侧的被解构值, 只要可以对它调用足够数量的组件函数(component function).
- componentN() 函数需要标记为 operator , 才可以在解构声明中使用.
举个例子,从一个函数返回两个值:
fun c(age: Int): User {
return User(age = age)
}
// 由于数据类会自动声明 componentN() 函数, 因此可以在这里使用解构声明.
val (name, age) = c(18)
println(name)
println(age)
但是有时候,我只想要解构部分属性怎么办?
class User(val name: String, val age: Int) {
operator fun component1(): Any {
return age
}
}
var user = User("name", 10)
val (comp1) = user
println(comp1)//输出10
解构声明与Map
operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()
operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
operator fun <K, V> Map.Entry<K, V>.component2() = getValue()
for ((key, value) in map) {
// 使用 key 和 value 执行某种操作
}
五、封闭类(Sealed class)
- 封闭类可以限制一个值只允许是某些指定的类型之一,而不允许是其他的类型。
- 要声明一个封闭类,需要将 sealed 修饰符放在类名之前.封闭类可以有子类,但所有的子类声明都必须嵌套在封闭类的声明部分之内.但是,从封闭类的子类再继承的子类(间接继承者)可以放在任何地方, 不必在封闭类的声明部分之内.
sealed class Expr {
class Const(val number: Double) : Expr()
class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// 不需要 `else` 分支, 因为我们已经覆盖了所有的可能情况
}