1. REPL:read、execute、print、loop
IntelliJ Idea、Android Studio等软件中的Kotlin实验工具,可直接运行代码片段。
2. 变量定义
val readOnlyVar : Type = value // val = value
var writableVar : Type = value // var = variable
val类型变量赋值后无法更改,编译时常量:
// 文件级定义,在类和方法外面定义,编译时赋值,函数内是运行时调用
const val constVar : Int = 1234
3. 类型推断
声明并赋值的变量,允许省略类型。
在IDE中,Ctrl+Shift+P可查看变量类型信息。
var strVar : String = "a string"
var strVar = "a string"
4. Kotlin中只有引用类型,没有基本类型(Java中小写字母开头的类型,比如int)
5. ===,!==
两个变量是否指向同一引用
6. 条件表达式
val msg = if (x == 1) {
"message 1"
} else if (x == 2) {
"message 2"
} else {
"unknown message"
}
7. range运算符
var num = 100
val msg = if (num in 1..9) {
"one digit"
} else if (num in 10..99) {
"two digits"
} else { "large num" }
println((1..3).toList())
[1, 2, 3]
println((1 until 3).toList())
[1, 2]
println((3 downTo 1).toList())
[3, 2, 1]
println(('a'..'d').toList())
[a, b, c, d]
8. when表达式
var greeting = when (name) {
"cat" -> "miaomiao"
"dog" -> "wangwang"
"tiger" -> "aoao"
}
var msg = when (num) {
in 1..9 -> "one digit"
in 10..99 -> "two digits"
else -> "many digits"
}
9. string template
println("Hello $yourname, ${if (age>18) "man" else "boy"}")
1)双引号字符串内使用
2){ }中可使用任何表达式
10. 函数
private fun funName(inPara : Int = 123) : String { }
1)默认是public
2)参数可设默认值
3)调用时,可指定参数名和参数值
private fund addMember(name="Tom", age=13)
4)反引号函数名,可以使用特殊字母定义函数,目的:解决和java的关键字冲突以及便于测试中命名函数。
fun `**~prolly not a good idea!~**`()
11. 单表达式函数
只有一个表达式语句或只有一个求值语句,返回类型、返回语句、花括号可省略
private fun add(val v1 : Int, val v2 : Int) : Int {
return v1 + v2
}
private fun add(val v1 : Int, val v2 : Int) = v1 + v2
12. Unit函数
无返回值的函数交Unit函数,返回类型是Unit。目的是为了解决泛型系统问题,java等语言void方案有问题。
private fun printMsg(msg : String) =
println("The message is: $msg")
13. Nothing类型
不可能执行完成,或抛出异常。
public inline fun TODO() : Nothing = throw NotImplementedError()
14. Lambda,匿名函数
1)函数体,lambda表达式。返回结果,lambda结果。
2)隐式返回:不能用return返回。
3)it关键字:如果只有一个参数,可以用参数名或者it(如果用it,可省略参数名声明)。
4)类型推断,省略函数变量的类型声明。
val testFunc : (String, Int) -> String = { name, age ->
"I am $name and $age year-old."
}
// 类型推断
val testFunc = { name : String, age : Int ->
"I am $name and $age year-old."
}
5)lambda做函数参数,返回值。
6)简略语法:
(1)函数最后一个函数是lambda,且调用函数时定义lambda。
(2)把lambda定义写在被调用函数的()外面。
(3)如果被调用函数只有一个lambda参数,(可省略)。
val add = { v1 : Int, v2 : Int ->
v1 + v2
}
fun calculate(i : Int, j : Int, op : (Int, Int) -> Int) : Int {
return op(i, j)
}
// call fucntion calculate
calculate(2, 3, add)
calculate(2, 3) { v1 : Int, v2 : Int ->
v1 + v2
}
// 仅有一个lambda参数,定义lambda时省略()
fun process(callback : ()->Unit) {
callback()
}
process {
println("In callback funciton.")
}
7)inline,lambda做为对象存在,内存消耗大。递归调用时,不能用inline。
inline fun process(callback : ()->Unit) { TODO() }
8)函数引用, :: + 函数名,相当于把已定义函数名转为lambda变量名
// 仅有一个lambda参数,定义lambda时省略()
fun process(callback : ()->Unit) {
callback()
}
fun myCallback() {
println("In callback funciton.")
}
process(::myCallback)
9)lambda是闭包。
15. 文件级变量定义时必须赋值,否则无法编译。局部变量限制松,使用前完成初始化即可。
16. 文件集函数对应java中的类静态方法。
17. 可空变量
1)在Java字节码中,通过@NotNull注解声明变量的非空属性。
var oneStr : String? = null
oneStr = "time"
// 安全调用操作符
oneStr?.capitalize()
oneStr?.let {
// 随便写语句,没什么逻辑关系
it.capitalize()
println(it)
}
// 非空断言操作符,感叹号操作符
oneStr!!.capitalize()
// 链式调用
oneStr?.capitalize()?.plus(" flies")
// 空合并操作符, Elvis操作符
var msg : String = oneStr ?: "A good day"
18. 异常
Kotlin所有异常都不强制捕获,unchecked异常。
Java异常分为checked和unchecked。
try {
// some code
} catch (e : Exception) {
println(e)
}
19. 先决条件函数, precondition function
1)checkNotNull,null,抛出异常;非null,返回值。
2)require,false,抛出异常。
3)requireNotNull,null,抛出异常;非null,返回值。
4)error,null,抛出异常并打印错误消息,非null,返回值。
5)assert,false,抛出异常,并打印断言编译器标记(待确认?)
20. 字符串操作
1)==
结构相等,===
引用相等 (例子??)
var s = "home/user"
var index = s.indexOf('/')
var s1 = s.substring(0, index) // home,参数是起止位置
var s2 = s.substring(0 until index) // home,参数是range
// 结构语法,最多前5个变量值
// _符号,过滤不需要的元素
val (d1, d2, d3, _) = "/home/user/local/bin".trim('/').split('/')
println("result: $d1, $d2, $d3\n") // result: home, user, local
oneString.replace(Regex("[aeiou]")) {
when (it.value) {
"a" -> "1"
"e" -> "2"
"i" -> "3"
"o" -> "4"
"u" -> "5"
else -> it.value
}
}
"hello".forEach {
println("$it\n")
}
/* result:
h
e
l
l
o
*/
21. 标准库函数
函数 | 获取receiver值 | 相关作用域 | 返回值 |
apply | no | yes | receiver |
run | no | yes | lambda结果 |
with | no | yes | lambda结果 |
let | yes | no | lambda结果 |
also | yes | no | receiver |
takeIf | yes | no | receiver? |
takeUnless | yes | no | receiver? |
1)相关作用域
(1)有相关作用域,调用receiver成员方法,对receiver本身进行修改。
(2)无相关作用域,则调用非成员方法,使用receiver值,不修改receiver本身。
(3)相关作用域和传receiver值是互斥的。
(4)相关作用域内可使用this。
2)返回值:
(1)返回lambda结果,多步调用的方法或者嵌套调用的方法,改为链式调用。
(2)返回receiver:可以链式调用。不同函数进行链式调用: str.apply{ }.let{ },而不是同类函数的链式调用,如:str.apply{ }.apply{ }
var str = "..."
var r1 : type1 = fun1(str)
var r2 : type2 = fun2(r1)
fun2(fun1(str))
str.run(::fun1)
.run(::fun2)
1)apply在作用域中对receiver进行批量操作。
(1)对同一个对象进行配置。
(2)如果receiver操作不返回对象本身,就不能进行链式调用,通过apply可以进行批量操作,达到链式调用的效果。
(3)在相关作用域中,调用receiver的成员方法。成员方法自带receiver的引用,所以不需要传入receiver值。
(4)apply返回receiver,run返回结果类型(也可能是receiver)。apply是对一个对象初始化(配置)后返回给变量,关注的是对象;run关注处理结果。
(5)with等同run,调用方式不同,不推荐。
2)let无相关作用域,传入receiver值。
(1)调用非receiver成员方法的函数进行操作,批量处理用到receiver值的操作。
(2)let中receiver值是只读的,且唯一参数,可通过it引用。
22. List
1)List.toList() toMutableList()
2)forEach()和forEachIndexed()适用于Iterable类型,如:List、Map、Set、IntRange
3)mutator操作:
add(value)
add(index, value)
addAll(list)
+= value/list
-= value/list
remove()
removeIf(lambda)
clear()
4)只有List支持解构,Set、Map不支持
// readonly
var strList : List<String> = listOf("abc", "def", "hij")
var str = strList[4]
var str = strList.getOrElse(4) { "no such element" }
var str = strList.getOrNull(4) ?: { "no such element" }
// get an random element
strList.shuffled().first()
// mutable
var strList = mutableListOf("abc", "def", "hij")
strList.remove("abc")
strList.add("xyz")
// retrieve
for (s in strList) {
println(s)
}
strList.forEach { s ->
println(s)
}
strList.forEachIndexed { index, str ->
}
23. Set
1)mutableSet操作:
add(value)
addAll(list)
+= values
-= values
remove(value)
removeAll(list)
clear
val planets = setOf("Mercury", "Venus", "Earth")
planets.elementAt(2)
var mySet = mutableSetOf<String>()
mySet += "one"
// set list转换,及元素去重
oneList.toSet().toList()
oneList.disctinct()
24. Map
1)map相关操作:
getValue(key), exception if not exist
getOrElse(key, lambda)
getOrDefault(key, default)
put(key, value)
putAll(ListOf<Pair>)
getOrPut(key, lambda)
remove(key)
- key
-= key
+=key
// to 是省略.和()的函数,返回一个pair
var nameMap = mapOf("dog" to "Wang", "cat" to "Miao")
// 等价
var nameMap = mapOf(Pair("dog", "Wang"), Pair("cat","Miao"))
var nameMap = mutableMapOf()
nameMap += "dog" to "Wang"
println(nameMap["dog"])
nameMap.forEach { animal, name ->
println("$animal is called $name.")
}
25. 类 class
1)类初始化顺序:
(1)主构造函数声明属性
(2)类级别的属性复制
(3)init块
(4)次构造函数
class Student(_name : String,
var mark : Int) { // mark属性在主构造函数中声明,默认getter和setter
// 覆盖默认getter和setter,可以使用field变量
// val类型变量没有setter,var类型变量有setter
var name : String
get() = "name:${field.capitalize()}"
set(str) {
field = str.trim()
}
// 计算属性,没有field保存变量值
val grade
get() = when(mark) {
100 -> "Supreme"
90..99 -> "Great"
70..89 -> "Good"
else -> "ok"
}
// 延迟初始化
lateinit var comment : String
// 惰性初始化,使用时再初始化
val address = by lazy { readFromDatabase() }
// 初始化块
init {
require(name.isNotBlank(), {"The name is requires!"})
}
// 次构造函数
// 不能定义类属性,类属性只能在主构造函数中或者类中定义
constructor(name : String) : this(name, 60)
}
var s = Student("tom", 88)
s.comment = "well done"
println("name: ${s.name}, mark: ${s.mark}, grade: ${s.grade}, comment:${s.comment}\n")
// name: name:Tom, mark: 88, grade: Good, comment:well done
26. 常用的Coroutine Scope
1)GlobalScope,不推荐,全局作用域,生命周期同Application,生命周期太长,超过Activity、Fragment的生命周期,不能在它们销毁时自动清理相关协程。
2)ViewModel.viewModelScope,推荐,生命周期同所属ViewModel,在器声明周期内自动管理协程。
3)LifecycleOwner.lifecycleScope,推荐,用于有LifecycleOwner的对象,如Fragment,根据LifecycleOwner生命周期自动管理协程。
27. Coroutine Dispatcher
Dispatcher | 运行线程 | 线程使用场景 |
Default | 线程池 | 适合cpu密集的操作,如后台计算等 |
Main | UI线程 | 刷新UI界面 |
IO | 线程池 | io操作,如网络请求、本地数据读写等 |
Unconfined | 不调度,直接执行 | 执行线程取决于挂起函数恢复的时候的调度器 |
28. 协程启动方式
启动方法 | 是否阻塞 | 是否返回值 |
runBlocking | 阻塞 | 无 |
launch | 不阻塞 | 无 |
withContext | 阻塞 | 返回最后一行代码的值 |
async | 不阻塞 | 返回Deferred<T>,用await获取 |
29. Flow
1)流式编程
和RxJava类似,流式编程的方式组织代码逻辑,优化异步、并发处理过程。
2)在协程内应用
3)冷流和热流
前者是由接收方触发执行,后者是发送方触发执行(没有接收方也会执行)。默认是冷流。
4)创建
// 动态数据
flow {
for (i in 1..5) {
emit(i)
}
}
flow {
// 实际中,先做一些运算,获得数据
emit(10)
// do something
emit(20)
// do something
emit(30)
}
// 静态数据
flowOf(1,2,3,4,5)
// 集合对象转化为Flow
list.asFlow()
5)使用示例
flow {
for (i in 1..5) {
emit(i)
}
}.onStart { // 启动回调,可选
log("flow starts.")
}.onCompletion { // 结束回调,可选
log("flow completes.")
}.collect { // 获取结果
log("get the result of a flow: $it")
}
6)接收方式
接收方法 | 功能说明 |
collect | 数据收集操作符,默认的flow是冷流,即当执行collect时,上游才会被触发执行。 |
collectIndexed | 带下标的收集操作,如collectIndexed{ index, value -> } |
collectLatest | 上游发送数据太快,则只接收最新数据,中间可能会抛弃部分数据 |
toList、toSet | 将flow{}结果转化为集合。 |
single | 确保流发射单个值 |
first | 仅仅取第一个值 |
reduce | 如果发射的是 Int,最终会得到一个 Int,可做累加操作 |
fold | reduce 的升级版,多了一个初始值 |
7)线程切换
操作符 | 功能 |
flowOn | 切换上游执行线程,即接收前的计算过程在哪个线程执行 |
lunchIn | 切换下游执行线程,即接收数据的操作在哪个协程中执行 |
8)转换操作符
操作符 | 功能说明 |
transform | 调用collect(),处理数据后,构建一个新的Flow,再执行emit()重新发出数据。 |
map | 相当于transform + emit |
filter | 根据指定条件过滤出部分数据 |
drop | 根据指定条件抛弃部分数据 |
distinctUntilChanged | 过滤重复数据,值变化时才通知下游 |
flatMapLatest | 优先发送新值,发送时未完成的操作会被取消 |
debounce | 防抖动函数 |
9)收发控制操作
上下游数据的发送和接收速度不匹配时,通过这些操作符进行调整、设置。类似RxJava的背压(Back Pressure)概念。
操作符 | 功能 |
buffer | 为流创建一个单独的协程,并发处理接收数据。收到的数据顺序可能和发生顺序不一致 |
conflate | 发送数据太快,只处理最新收到的,可能会丢失数据 |