diff --git a/Learning/深入Kotlin.md b/Learning/深入Kotlin.md index 408c64a..6a2963a 100644 --- a/Learning/深入Kotlin.md +++ b/Learning/深入Kotlin.md @@ -173,46 +173,438 @@ class Square() : Rectangle(), Polygon { #### 接口 -接口 +接口里面可以有: + +- 抽象函数(没有函数体) +- 带默认实现的函数 +- 抽象属性 +- 一个写明类型的变量,但不能有默认值 + +```kotlin +interface Clickable { + fun click() + fun showOff() = println("I'm clickable") + val name: String +} +``` #### 抽象类 -抽象类介于接口和类之间, +抽象类介于接口和普通类之间: + +- 可以有构造参数、状态、成员变量 +- 可以有抽象成员,也可以有具体实现,如果成员变量有默认值,那它就不是`抽象的` +- 抽象成员默认是 `open` 的,子类必须 `override` +- 一个类只能继承一个抽象类,但可以实现多个接口 + +```kotlin +abstract class Animal { + abstract val name: String + abstract fun sound() + + open fun info() = "$name says ${sound()}" +} + +class Dog(override val name: String) : Animal() { + override fun sound() = "Woof" +} +``` ### 泛型类 +泛型类把类型作为参数: + +```kotlin +class Box(var value: T) + +val stringBox = Box("hello") +val intBox = Box(123) +``` + +#### 类型约束 + +```kotlin +class NumberBox(val value: T) +class NumberBox(val value: T) where T : Serializable, T : Number +``` + +使用 `where` 给 `T` 更多限定,这里,`T` 既是 `Serializable` 的子类,也是 `Number` 的子类。 + +#### 型变 + +```kotlin +// out:协变,只能返回 T 类型变量 +class Producer(private val value: T) { + fun get(): T = value +} + +// in:逆变,只能接收 T 类型变量 +class Consumer { + fun consume(value: T) {} +} +``` + +#### 星投影 + +具体的泛型类型,依据传进来的 list 参数,它允许任意的唯一类型。与 Any 不同的是,使用 Any 指 list 本身的泛型就是 Any。 + +```kotlin +fun printAll(list: List<*>) { + list.forEach { println(it) } +} +``` + ### 特殊类 #### object +它常用于单例、匿名对象、伴生对象。 + +##### 单例 + +单例可以直接用类名当成对象变量名使用,而且每次使用时调用的对象都是同一个。 + + +```kotlin +object Config { + const val STR = "const" // object 里面可以定义 const 变量 + var debug = false + fun log(msg: String) { + if (debug) println(msg) + } +} +``` + +##### 匿名对象 + +它用于实现一个接口,或是别的什么东西。 + +```kotlin +val runnable = object : Runnable { + override fun run() { + println("running") + } +} +``` + +##### 伴生对象 + +```kotlin +class User private constructor(val name: String) { + companion object { + const val TABLE = "users" + fun create(name: String) = User(name) + } // 用一个工厂方法代替构造函数,这很常用,它可以做一些更复杂的事。 +} + +val u = User.create("Tom") +println(User.TABLE) +``` + #### data class +数据类自动生成: + +- `equals()` +- `hashCode()` +- `toString()` +- `copy()` +- `componentN()` + +```kotlin +data class User(val name: String, var age: Int) + +val u = User("Tom", 18) +val u2 = u.copy(age = 19) +val (name, age) = u +``` + +- 主构造至少有一个参数 +- 主构造参数必须定义一个成员变量 +- 不能是 `abstract`、`open`、`sealed`、`inner` + #### enum class +枚举类表示一组有限常量。 + +```kotlin +enum class Direction( + val degree: Int // 它指的是枚举变量的属性 +) { + NORTH(0), + EAST(90), + SOUTH(180), + WEST(270); + + // entries 是 Direction 的成员变量,是所有枚举变量组成的列表 + fun turnRight(): Direction = entries[(ordinal + 1) % entries.size] +} + +val d = Direction.NORTH +println(d.degree) +``` + +枚举可以实现接口,但不能继承普通类。每个枚举常量本身是对象。 + ### 特殊类函数 #### 重写 toString +```kotlin +class Point(val x: Int, val y: Int) { + override fun toString(): String = "Point($x, $y)" +} + +println(Point(1, 2)) // Point(1, 2) +``` + +`data class` 会自动生成 `toString()`。 + #### 重载运算符 +用 `operator` 修饰函数,让对象支持运算符。常见映射: + +| 运算符 | 函数 | +| :-------: | :-----------: | +| + | plus | +| - | minus | +| * | times | +| / | div | +| % | rem | +| += 等 | plusAssign 等 | +| [] | get / set | +| () | invoke | +| in | contains | +| < > <= >= | compareTo | +| .. | rangeTo | +| == | equals | + +`compareTo` 函数较为特殊,它返回 `Int`,意思是这样的:a < b 等价于 a.compareTo(b) < 0。这与 CPP / Java 是一样的。 + +更多函数名请查询 [运算符重载](https://book.kotlincn.net/text/operator-overloading.html) + +```kotlin +data class Vec(val x: Int, val y: Int) { + operator fun plus(other: Vec) = Vec(x + other.x, y + other.y) + operator fun get(index: Int) = if (index == 0) x else y +} + +val v = Vec(1, 2) + Vec(3, 4) +println(v[0]) +``` + #### component 函数 -#### invoke +`componentN()` 用于解构声明。 -类似于 Python 的 `__call__()` +```kotlin +class User(val name: String, val age: Int) { + operator fun component1() = name + operator fun component2() = age +} + +val (name, age) = User("Tom", 18) +``` + +`data class` 会自动生成 `component1()`、`component2()` 等。 + +#### invoke 函数 + +`invoke` 让实例可以像函数一样调用,类似 Python 的 `__call__()`。 + +```kotlin +class Adder(val base: Int) { + operator fun invoke(x: Int): Int = base + x +} + +val add10 = Adder(10) +println(add10(5)) // 15 +``` ## 特殊函数 ### 嵌套 lambda 与标签 +标签语法:`label@`。 + +```kotlin +outer@ for (i in 1..3) { + for (j in 1..3) { + if (j == 2) continue@outer + if (i == 3) break@outer + } +} +``` + +lambda 中常用 `return@label`: + +```kotlin +listOf(1, 2, 3).forEach lit@{ + if (it == 2) return@lit + println(it) +} +``` + +如果 lambda 是 `inline` 的,可以直接非局部返回: + +```kotlin +fun foo() { + listOf(1, 2, 3).forEach { + if (it == 2) return // 从 foo 返回 + } +} +``` + +嵌套 lambda 中同名隐式标签可能冲突,建议显式命名标签。 + ### 泛型函数 +```kotlin +fun singletonList(item: T): List = listOf(item) + +fun > max(a: T, b: T): T = if (a > b) a else b +``` + +多个约束用 `where`: + +```kotlin +fun ensureTrailingPeriod(seq: T) +where T : CharSequence, T : Appendable { + // 在这个函数内,T 需要同时为 CharSequence 和 Appendable 的子类。 + // 这样,seq 就能调用两个父类的函数。 + if (!seq.endsWith('.')) seq.append('.') +} +``` + ### infix 函数 +`infix` 函数必须是成员函数或扩展函数,并且: + +- 只有一个参数 +- 参数不能是可变参数 +- 参数不能有默认值 + +```kotlin +infix fun String.hasPrefix(prefix: String): Boolean = startsWith(prefix) + +val ok = "Kotlin" hasPrefix "Kot" // infix 函数有独特写法 +``` + +标准库中的 `to` 也是 infix: + +```kotlin +val pair = "name" to "Tom" +``` + +### inline 函数 + +`inline` 会把函数体和 lambda 参数内联到调用处,减少 lambda 对象开销,并允许非局部返回。 + +```kotlin +inline fun withLock(lock: Lock, block: () -> T): T { + lock.lock() + try { + return block() + } finally { + lock.unlock() + } +} +``` + +`noinline`:不内联某个 lambda 参数,可把它当对象传递。 + +```kotlin +inline fun foo(noinline block: () -> Unit) { + val f = block + f() +} +``` + +`crossinline`:内联 lambda 中禁止非局部返回。 + +```kotlin +inline fun foo(crossinline block: () -> Unit) { + Runnable { block() }.run() +} +``` + +### 泛型 reified + +普通泛型有类型擦除,不能直接 `is T` 或 `T::class`。 +配合 `inline` 和 `reified` 可以保留类型信息。 + +```kotlin +inline fun Any.isInstanceOf(): Boolean = this is T + +inline fun List<*>.filterIsInstance(): List { + val result = mutableListOf() + for (item in this) { + if (item is T) result.add(item) + } + return result +} +``` + +`reified` 只能用于 `inline` 函数。 + ### 扩展函数 +扩展函数可以在不修改原类的情况下,为类增加函数。 + +```kotlin +fun String.lastChar(): Char = this[length - 1] + +val String.lastChar: Char + get() = this[length - 1] +``` + +扩展函数是独立于类定义的,使用时也需要额外的 `import`。 + +```kotlin +open class A +class B : A() + +fun A.foo() = "A" +fun B.foo() = "B" + +val a: A = B() +println(a.foo()) // A +``` + +成员函数优先于扩展函数: + +```kotlin +class C { + fun foo() = "member" +} + +fun C.foo() = "extension" + +println(C().foo()) // member +``` + #### 概念与 lambda 变量类型 +普通函数类型: + +```kotlin +val f1: (String) -> Int = { it.length } +``` + +带接收者的函数类型: + +```kotlin +val f2: String.() -> Int = { length } + +println("abc".f2()) // 3 +println(f2("abc")) // 3 +``` + +区别: + +- `(String) -> Int`:参数在 lambda 中用 `it` 或命名参数访问 +- `String.() -> Int`:接收者在 lambda 中用 `this` 访问,而且,相当与直接在类 `String` 中定义函数 + #### 常用的标准库扩展函数 ##### apply