深入kotlin-数据类和密封类

Posted 颐和园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入kotlin-数据类和密封类相关的知识,希望对你有一定的参考价值。

Data class

定义

Kotlin 中对 java 中的 pojo 类型进行了专门的支持和简化,叫做数据类(data class)。一个数据类的定义非常简单,仅有一个主构造方法即可:

data class Person(val name: String, var age:Int, var address:String)

// 使用 data class
var person = Person("zhangshan",30,"shanghai")
println(person)// 输出 Person(name=zhangshan, age=30, address=shanghai)

数据类的定义有如下特点:

  • 主构造方法至少有一个参数。

  • 所有的主构造参数必须声明为属性,即必须声明可变性,要么 val,要么 var。

  • 数据类不能说 abstract\\open\\sealed\\internal

自动创建的方法

从中不难猜出,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(name=zhangshan, age=30, address=hangzhou)

这样只是覆盖了 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) // 1

... ...
val (name, age) = test() // 2
  1. test 函数返回一个 Pair,在 Pair 中包含了两个值。
  2. 利用解构声明,读取返回值中的两个值。在 Pair 中,这两个值也可以用 first 和 second 来访问。

Map 的解构

val map = mapOf("a" to "aa","b" to "bb", "c" to "cc")
for((key, value) in map) // 1
	println("key:$key, value:$value")

map.mapValues  // 2
  (_, value) -> "$value hello" // 3

  1. 利用解构声明遍历 key 和 value。
  2. mapValues 方法可以将 map 中的所有 value 转换成另外一个值和类型,key 不变。mapKeys 则转换 key 的值和类型。
  3. 这里我们将每个 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 子句。

所以密封类更像是一种枚举类型,用于表示父类和子类之间的关系是可列举的,而枚举类型用于表示类型和实例之间的关系是可列举的。

以上是关于深入kotlin-数据类和密封类的主要内容,如果未能解决你的问题,请参考以下文章

深入kotlin-数据类和密封类

kotlin学习总结——object关键字数据类密封类嵌套类和内部类

Kotlin中嵌套类数据类枚举类和密封类的详解

Kotlin中嵌套类数据类枚举类和密封类的详解

Kotlin——最详细的数据类密封类详解

kotlin sealed密封类