本章节主要介绍Kotlin类型一些不为人知的秘密

image-20211118202509898

image-20211118202537590

构造器

构造器的基本写法

1
2
3
4
class Person(
var age: Int, // 类内全局可见
name: String // 构造器内可见(init块,属性初始化)
)

init块

init块可以有多个

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person(var age: Int, name: String) {
var name: String

init {
this.name = name
}

val firstName = name.split(" ")[0]

init {
// ...
}
}

属性必须被初始化

Image

继承父类

1
2
abstract class Animal
class Person(var age: Int, var name: String) : Animal() // 调用父类构造器

副构造器

1
2
3
class Person(var age: Int, var name: String) : Animal() {
constructor(age: Int) : this(age, "unknown") // 副构造器,调用主构造器,确保构造路径唯一性
}

不定义主构造器(不推荐)

Image

主构造器默认参数

可以为主构造器定义默认参数,使用@JvmOverloads可以在Java代码中以重载的形式调用

Image

使用同名函数作为工厂函数

1
2
3
4
5
val persons = HashMap<String, Person>()
fun Person(name: String): Person {
return persons[name]
?: Person(1, name).also { persons[name] = it }
}

可见性

类的可见性

可见性类型 Java Kotlin
public 公开 与Java相同,默认
internal 模块内可见
default 包内可见,默认
protected 包内及子类可见 类内及子类可见
private 类内可见 类或文件内可见

修饰对象

可见性类型 顶级声明 成员
public ✔️ ✔️ ✔️
internal ✔️,模块 ✔️,模块 ✔️,模块
protected ✔️
private ✔️,文件 ✔️,文件 ✔️,类

模块

直观的讲,大致可以认为是一个Jar包、一个aar

internal VS default

  • 一般由SDK或公共组件开发者用于隐藏模块内部细节实现
  • default可通过外部创建相同包名来访问,访问控制非常弱
  • default会导致不同抽象层次的类聚集到相同包之下
  • internal可方便处理内外隔离,提升模块内聚减少接口暴露
  • internal修饰的kotlin类或成员在Java当中可直接访问

类的可见性

1
2
class Person
private constructor(var age: Int, var name: String) // 构造器私有化

属性的可见性

1
2
3
4
5
6
7
class Person(var age: Int, var name: String) {
private var firstName: String = "" // 私有化属性 firstName,外部无法访问
var secondName: String = ""
private set // 私有化属性secondName的setter,外部只能读取
private get // 编译器报错,getter的可见性必须与属性可见性一致
public set // 编译器报错,setter的可见性不得大于属性的可见性
}

顶级声明的可见性

  • 顶级声明指文件内直接定义的属性、函数、类等
  • 顶级声明不支持protected
  • 顶级声明被private修饰标识文件内部可见

密封类(sealed)

  • 密封类是一种特殊的抽象类
  • 密封类的子类定义在与自身相同的文件中
  • 密封类的子类个数是有限的

Image

密封类的子类

1
2
3
4
5
6
7
8
9
10
11
12
sealed class PlayerState

object Idle : PlayerState()

class Playing(val song: Song) : PlayerState() {
fun start() {}
fun stop() {}
}

class Error(val errorInfo: ErrorInfo) : PlayerState() {
fun recover() {}
}

子类分支

子类可数,分支完备,所以不需要else分支

1
2
3
4
5
6
7
8
9
10
11
12
13
this.state = when (val state = this.state) {
Idle -> {
Playing(song).also(Playing::start)
}
is Playing -> {
state.stop()
Playing(song).also(Playing::start)
}
is Error -> {
state.recover()
Playing(song).also(Playing::start)
}
}

密封类VS枚举类

密封类 枚举类
状态实现 子类继承 类实例化
状态可数 子类可数 实例可数
状态差异 类型差异 值差异

内联类(inline)

  • 内联类是对某一个类型的包装
  • 内联类是类似于Java装箱类型的一种类型
  • 编译器会尽可能使用被包装的类型进行优化
  • 内联类在1.3中处于公测阶段,谨慎使用

Image

内联类可以实现接口,但不能继承父类,也不能被继承

内联类的限制

  • 主构造器必须有且只有一个只读属性
  • 不能定义有backing-field的其他属性
  • 被包装类型必须不能是泛型类型
  • 不能继承父类也不能被继承
  • 内联类不能定义为其他类的内部类

内联类 VS 类型别名

typealias inline class
类型 没有新类型 有包装类型产生
实例 与原类型一致 必要时使用包装类型
场景 类型更直观 优化包装类型性能