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)Android kotlin 将指定时间戳秒数和毫秒数设置为0 kotlin emit_java{ }中可使用任何表达式

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

发送数据太快,只处理最新收到的,可能会丢失数据