《Kotlin 核心编程》读书笔记

第四章

密封类 sealed

密封类用来表示受限的类继承结构:当一个值为有限集中的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。

第五章 类型系统

as 不安全的类型转换,转换失败会抛类型转换失败异常。使用 as? 转换失败返回 null。

Int 等同于 int

Int? 等同于 Integer

协变与不变

java 中数组是协变的,意思就是任意 A 是 B 的父类,则 A[] 也是 B[] 的父类。

List 是不变的,只知道自己是一个 Lit,无法获取泛型参数的类型。Kotlin 中数值支持泛型,当然也不再协变。Any 是所有类的父类,但是你不能将任意一个对象数组赋值给 Array

Kotlin 中 List 是协变的。因为 Kotlin 中 List 的定义是 publick interface List<out E> : Collection<E> {},out 关键字,说明泛型类和泛型方法是协变的。为了保证类型安全,kotlin 中的 List 是无法添加元素的。

获取泛型类型

  1. 通过匿名内部类

    泛型虽然是类型擦除的,但是类型信息是放在对应 class 的常量池中的。匿名内部类在初始化的时候会绑定父类或父接口的相应信息。

    1
    2
    val list = object: ArrayLisr<String>()
    print(list.javaClass.genericSuperclass)
  1. 使用内联函数

    kotlin 内联函数在编译的时候编译期会将相应的字节码插入调用的地方,也就是说,参数类型也会被插入字节码中。

    1
    2
    3
    4
    //在kotlin中一个内联函数(inline)可以被具体化(reified),这意味着我们可以得到使用泛型类型的Class
    inline fun <reified T> getType() {
    return T::class.java
    }
需要注意的是,Java 并不支持指定一个函数是否内联,所以 reified 来实例化的内联函数不能在 Java 中调用,因为它永远是需要内联的。

协变和逆变

-> Java 中的 <? extends T>

-> Java 中的 <? super T>

第 6 章 Lambda 和集合

序列 sequence

序列是惰性求值(延时求值)的,普通集合进行链式操作是(例如 filter、map),每一步都会先产生新的集合,而序列则是将所有操作都应用在一个元素上,当第一个元素执行完 filter、map 后,第二个元素再继续执行 filter、map。

与 Java8 的流(Stream)不同,流是一次性的,只能遍历一次。

内联函数

inline 修饰的函数,会被内联进调用它的地方。直接在字节码中生成相应的函数体实现

实现非局部返回

1
2
3
4
5
6
7
8
9
fun localReturn() {
return
}
fun foo() {
print("1")
localReturn()
print("2")
}
//打印出12

因为函数体中的 return 只会在该函数的局部生效

1
2
3
4
5
6
fun foo(returning: () -> Unit) {
print("1")
returning()
print("2")
}
//报错:Lambda 中不允许存在 return

这时可以使用内联函数,

1
2
3
4
5
inline fun foo(returning: () -> Unit) {
print("1")
returning()
print("2")
}

还可以使用标签实现 Lambda 非局部返回

1
2
3
fun main() {
foo {return@foo}
}

第七章 多态和扩展

运算符重载

operator 关键字叫上 Kotlin 规定的函数名:plus(加法),minus(减法),times(乘法),div(除法),mod(取余)等

1
2
3
4
5
6
7
8
9
data class Area(val valus: Double)

operator fun Area.plus(that: Area): Area {
return Area(this.value + that.value)
}

fun main() {
print(Area(1.0) + Area(2.0))
}

扩展函数实现机制

可以将扩展函数理解为静态方法。

使用扩展函数添加属性:

1
2
val Mutablelist<Int>.sumIsEven: Boolean
get() = this.sum() % 2 == 0

静态与动态调度

调用重载方法时,调用变为静态并且取决于编译时类型

1
2
3
4
5
6
void foo(Base base) {}

void foo(Extanded base) {}

Base base = new Extended()
foo(base) //调用的是 Base 参数类型的方法

第九章 设计模式

装饰者模式

  1. 使用类委托减少样板代码

使用 by 关键字,将装饰类的所有方法委托给被装饰对象,然后只需要复写需要装饰的方法即可。

  1. 使用扩展函数代替装饰者

第十章 函数式编程

惰性求值

也称正则序,当用到时才求值

第十一章 异步和并发

同步与异步

同步指的是一个行为,当执行的时候,在代码层面需要我们主动去等待结果,直到结果返回。

多线程执行时看上去是同步执行,但是线程执行是通过 CPU 调度,CPU 在每个线程间快速切换,同一个时间片内只能执行一个线程。

协程

协程是一个无优先级的子程序调度组件,允许子程序。线程包含于进程,协程包含于线程。只要内存只够, 一个线程中可以有任意多个协程,但某一个时刻只能有一个协程在运行,多个协程共享该线程分配到的计算机资源。

launch 和 runBlocking

runBloking 是最高级的协程,也就是主协程。launch 创建的线程能够在 runBlocking 中运行,反过来不行。runBlocking 会阻塞当前执行的线程。

join

1
2
3
4
val job = launch {
search()
}
job.join()

join 后程序会一直等待,直到启动的协程结束。这里的等待是非阻塞式的等待,不会将当前线程挂起。

async

会创建一个子协程,会和其他子协程一样并行工作。async 会返回一个 Deferred 对象。Deferred 值是一个非阻塞可取消的 future,是一个带有结果的 job。

future 的意思是,将来会返回一个结果,利用 await 方法可以等待这个值的结果。