前言
《 Kotlin 实战 》读书笔记(5~6章 & 附录及其他)。
《 Kotlin 实战 》读书笔记
5 Lambda 编程
Lambda,可以作为函数参数的代码块
1 | val sum = {x: Int, y: Int -> x + y} |
集合的函数式 API
- fliter 过滤 (保留符合 predicate=true 的值)
- map 变换
- all,any,count,find
- groupBy 列表按 lambda 表达式提供的字段key 转换为 Map<key,List
> - flatMap 先对集合中的每个元素做 map 操作,然后把列表合并和一个
flatten 直接平铺(合并)
1 | fun main() { |
output
1 | [efg, hi, mike] |
合理使用搭配操作符,避免出现相同操作被执行多次的情况,或者优先筛选,避免无谓的迭代查找
惰性集合操作
以上 API 操作,每一个操作符在中间会生成一系列的列表,即生成了许多中间的暂时对象,当列表数量巨大时,产生的开销也是巨大的,因此需要优化
1 | fun userSequence() { |
output
1 | [<c>, <d>, <efg>] |
只有在执行最后一个操作符(比如上面的 toList())时才会进行所有运算。 其实和 Java8 中 Stream 是类似的。
函数式接口
SAM
lambda 表达式转换成函数式接口
1 | val run = Runnable { println("just run") } |
带接收者的 lambda :with & apply
with
方法原型
1 | public inline fun <T, R> with(receiver: T, block: T.() -> R): R { |
apply
1 | internal.InlineOnly . |
首先从函数原型可以看出,二者都是对接收者对象进行了 lambda 表达式的操作。with 返回的是lambda 表达式结果, apply 返回的永远都是接收者对象本身。结合下面的例子:
使用方式:
1 | val result = with(StringBuilder()) { |
1 | 1 |
结果和方法定义也是如出一辙。因此这就是二者的区别。apply 本质上是一个扩展函数。带接收者的 lambda 是构建 DSL 的好工具。
6 Kotlin 的类型系统
Elvis 运算符 “?:”
1 | fun foo(s: String?) { |
!! 非空断言
1 | fun foo(s: String?) { |
此时,在 s!! 的时候立即会发生 npe,而不是在使用的时候。
对于控制还可以使用 let 操作符
1 | // 使用 |
可以看到 let 函数和之前的 apply 函数有些相似,都是扩展函数,但是 let 返回的是 lamdba 执行的结果,而 apply 返回的永远都是接收者对象;再有和 let 和 with 也是不一样的,虽然都是返回 block 执行结果,但是with 的默认参数是 this 因此可以直接调用接收者方法,而 let 默认参数是 it ;必须用 it 才能调用接收者的方法。
lateinit
个人经验,lateinit 比较坑,尽量不用吧
1 | private lateinit var mm :Any |
类型参数的可空
在 Kotlin 中所有泛型类和泛型函数的类似参数默认都是可空的。
1 | fun <T> printHash(t: T) { |
类型参数推导出的类型是 Any?,因此 T 虽然声明非空,但是其实是可空的。为了避免这种情况,可以这样声明:
1 | fun <T :Any> printHash(t: T) { |
可控性和 Java
包含注解
首先,如果 Java 代码写的比较规范,那么代码中
1
2+ Type == Type?
+ Type == Type如果 Type 没有注解,那么在 Kotlin 中会被当做平台类型
平台类型
不知道可空性信息的类型,即使用者既可以当做空类型,也可以当做非空类型。进行操作是,自己决定是否需要判空。
常见的错误类型信息
1
Type mismatch: inferred type is String,but Int was expected
在用 Kotlin 继承实现 Java 的类时,一定要确定好参数的可控性,不要随意删掉参数后的 ?。Kotlin 编译其会为你声明的每一个非空的参数生成非空断言,如果 java 代码传递了一个 null,断言就会触发,即便你从来都没用过这个参数
基本数据类型和其他类型
Kotlin 不区分基本数据类型和包装类型
Any,Unit & Nothing
- Any 所表达的含义类似于 Java 中的 Object。但是依旧要区分 Any 和 Any?。但是 Any 并不是 Object,没有继承关系。
1
2
3
4
5
6
7
8
9public open class Any {
public open operator fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String
}并没有 Object 的 wait 及 notify 等方法。
- Unit 表达 Java 中 void 的含义。大部分情况作为函数返回值使用,没有什么特别的。**在 Kotlin 中,Unit 是一个完备的类型,可以作为参数类型(就是泛型尖括号里内容),但是 void 是不行的。而且他有唯一的值,这个值也是 Unit,在返回为 Unit 的函数中会被隐式的返回。
1
2
3
4
5
6
7
8
9
10interface Processor<T> {
fun proc(): T
}
class NoResultProcessor : Processor<Unit> {
override fun proc() {
// 底层帮你显示的返回了这句,不用自己写
return Unit
}
}这个特性其实非常好,类似 Callable
和 Runnable 其实并不需要重复定义了(当然只是对纯粹使用 Kotlin 的实现来说),比较在 Java 中为了表达返返回的泛型为空这件事代码得这么写 1
2
3
4
5
6
7
8
9
10
11private static void simpleCallable() {
Callable call = new Callable<Void>() {
public Void call() throws Exception {
// 必须显示的写 return 语句
return null;
}
};
}- Nothing
这个函数永不返回,一般用在抛出异常等场景。
集合和数组
集合
Kotlin 中结合区分为可变集合不可变集合。
集合类型 只读 可变 List listOf mutableListOf,arrayListOf Set setOf mutableSetOf,hashSetOf,linkedSetOf,sortedSetOf Map mapOf mutableMapOf,hashMapOf,linkedMapOf,sortedMapOf 在 Kotlin 中使用 Java 中定义的方法是,对于方法中集合类型,需要结合实际业务决定集合的可空性、可变性等因素。
数组
引用类型数组
1
2
3
4
5
6
7
8
9// 引用类型的数组声明及创建方式
val strings: Array<String> = arrayOf("a", "b", "c")
println(strings.joinToString())
val peoples: Array<People?> = arrayOfNulls<People>(5)
println(peoples.joinToString())
val people :Array<People> = Array(3) {
People("mike $it","${it*it}".toInt())
}
println(people.joinToString())1
2
3a, b, c
null, null, null, null, null
People(name=mike 0, age=0), People(name=mike 1, age=1), People(name=mike 2, age=4)引用类型,有 3 中方法可以创建数组,可以满足日常需求了。
基础类型数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 基本数据类型的数组
val ints: Array<Int> = arrayOf(1, 2, 3, 4)
println(ints.javaClass)
println(ints.joinToString())
// 基础数据类型数组创建方式 1
val ints0 = IntArray(5)
println(ints0.javaClass)
println(ints0.joinToString())
// 基础数据类型数组创建方式 2
val ints1 = intArrayOf(0, 1, 2, 4)
println(ints1.joinToString())
// 基础数据类型数组创建方式 3
val ints2 = IntArray(4) { it * it }
println(ints2.joinToString())
// [Integer] 转换为 [int]
val ints3: IntArray = arrayOf(1, 2, 3, 4).toIntArray()
println(ints3.joinToString())1
2
3
4
5
6
7class [Ljava.lang.Integer;
1, 2, 3, 4
class [I
0, 0, 0, 0, 0
0, 1, 2, 4
0, 1, 4, 9
1, 2, 3, 4IntArray 对应于 int[],而 Array
对应于 Integer[],因此如果需要使用数组,应该经量选择 IntArray,ByteArray,CharArray 这些真正的基础类型。当然,也可以通过装箱类型的数组做转换。 数组遍历
1
2
3
4// 带下标的遍历方式
for (i in ints2.indices) {
print(i)
}
其他
在这本书里始终没有提到,但是有时候又会用到的东西。
位操作符
1 | shl(bits) – 左移位 (Java’s <<) |
附录
- 文档相关
类似于 Java 的 Javadoc ,Kotlin 有 KDoc。 有 Java 相比有部分差异。Kotlin 的文档生成工具叫做
Dokka