Data class
定义
Kotlin 中对 java 中的 pojo 类型进行了专门的支持和简化,叫做数据类(data class)。一个数据类的定义非常简单,仅有一个主构造方法即可:
data class Person(val name: String, var age:Int, var address:String)
var person = Person("zhangshan",30,"shanghai")
println(person)
数据类的定义有如下特点:
自动创建的方法
从中不难猜出,data class 和 lombok 一样,自动为 Person 类创建了一系列的方法,比如:
- getter/setter
- toString 方法
- equals 和 hashCode 方法。
- component1…N 方法,按照属性声明顺序定义。
- copy 方法,用于对象复制。
继承和覆盖
数据类的成员可以被重写,但有一些限制:
- 如果数据类中显式定义了 equals\hashCode\toString 方法,或者数据类的父类中将这些方法指定为 final,那么编译器不会创建这些方法,转而使用显式定义的方法。
- 如果父类中定义了 componentN 方法,并且方法是 open 且和编译器生成的方法类型兼容,那么编译器生成的方法会被重写父类方法;如果父类中这些方法类型不兼容或者被定义为 final,则编译器会报错。
- 不允许重写数据类的 componentN/copy 方法(允许重载,但不允许重写)。
copy 方法
数据类的 copy 方法用于对象复制,特别是对于大部分属性相同,但有部分属性不同的对象:
var person = Person("zhangshan",30,"shanghai")
var person2 = person.copy(address="hangzhou")
println(person2)
这样只是覆盖了 person 对象的 address 值,其它属性原样复制。调用 copy 方法时,最好指定参数名,如果不指定,默认会按照参数顺序传参,这个结果可能不可预期的。
访问属性
数据类的属性访问都是可以直接访问的:
person.age = 20
println(person.age)
注意,虽然编译器为每个属性生成了 geter/setter 方法,但那是字节码,在 kotlin 中这些方法是不可访问的。
componentN 方法和解构声明
数据类的主方法中声明的每个参数,都会有一个对应的componentN 方法,N 被参数的顺序替换,从 1 开始。
componentN 方法返回的其实就是每个属性的对应字段,这些方法实现了 kotilin 的解构声明。
什么叫解构声明?其实就是 decode,我们可以使用如下语法将一个数据类的属性读取到一个元组(同 Swift Tuple):
var (name, age, address)=person
println("{$name,$address,$age}")
解构时,person 的属性依照声明的先后顺序读取到左边的变量中。
在 kotlin 中,解构声明通常会和数据类一起使用:
data class Person(val name: String, val age: Int)
... ...
val (name,age) = Person("zhangsan",30)
println(name)
println(age)
解构声明可以通过类型推断获知 component 的类型,但也可以显示指定 component 的类型,可以全部指定,也可部分指定。
val (_, age:Int) = Person("zhangsan", 30)
Pair 和 Triple
Pair 和 Triple 用于让函数突破只能返回单个值的限制。Pair 返回 2 个值,Triple 返回 3 个值。
fun test(): Pair<String,Int>{
return Pair("Zhansan",40)
}
... ...
val (name, age) = test()
- test 函数返回一个 Pair,在 Pair 中包含了两个值。
- 利用解构声明,读取返回值中的两个值。在 Pair 中,这两个值也可以用 first 和 second 来访问。
Map 的解构
val map = mapOf("a" to "aa","b" to "bb", "c" to "cc")
for((key, value) in map){
println("key:$key, value:$value")
}
map.mapValues {
(_, value) -> "$value hello"
}
- 利用解构声明遍历 key 和 value。
- mapValues 方法可以将 map 中的所有 value 转换成另外一个值和类型,key 不变。mapKeys 则转换 key 的值和类型。
- 这里我们将每个 value 后面加上了一个 hello。类型仍然是 String。
无参构造器
数据类要求主构造方法至少有一个参数,如果需要使用无参构造方法,只能为每个参数提供默认值来实现:
data class Person(val name: String="", var age:Int=0, var address:String="")
在字节码层级,就会生成一个无参的构造方法。
密封类
Kotlin 用密封类来描述子类受限制的类。
- 密封类的子类必须和密封类位于同一文件(kotlin 1.1 以后)。
- 密封类的子类如果不是密封的,那么它的子类可以定义在任何地方。
- 密封类是抽象的,不能实例化。
- 不允许非私有的构造方法。
密封类的一种常见应用场景是在 when 语句中使用,使得不必提供 else:
sealed class Calculator()
class Add:Calculator
class Subtract:Calculator
fun calculate(a: Int, b: Int, cal: Calculator) = when(cal) {
is Add -> a+b
is Subtract -> a-b
}
fun main(args: Array<String>){
println(caculate(1,2, Add()))
println(caculate(3,2, Substract()))
}
when 语句中对密封类的所有子类进行枚举,如果 cal 对象属于该密封类的特定子类时,执行特定的操作。由于密封类的子类已经在 when 表达式内部列举尽了,所以无需 else 子句。
所以密封类更像是一种枚举类型,用于表示父类和子类之间的关系是可列举的,而枚举类型用于表示类型和实例之间的关系是可列举的。
|