kotlin学习指南一
kotlin 是 google 推出的一門新語言,也是基於 JVM 的。
kotlin 基础
hello world
新建一个 kt 文件,
1 | fun main(args:Array<String>) { |
程序主入口 main 方法, 无需新建类即可运行,函数声明用 fun
1 | fun sum(a:Int, b:Int):Int{ |
返回值写在参数之后
表达式可以作为函数体,返回值类型自动推断
1 | fun sun(a:Int, b:Int) = a+b; |
返回无意义的值用 Unit 表示
fun test():Unit {
println(“test”);
}
变量赋值
变量赋值声明关键字:var、val
var:声明变量
val:声明常量
var i:Int = 1;
val PI:Double = 3.14;
当声明并赋值时,数据类型可以省略, 会自动推断数据类型
var i = 1;
val PI = 3.14
当声明但不赋值时,不能省略数据类型
var i:Int;
val PI:Double;
字符串模板
1 | var s = "aaaaa"; |
条件表达式
1 | fun testIf(a:Int, b:Int):Int { |
在 kotlin 中,if 还可以用作表达式
1 | fun max(a:Int, b:Int) = if(a > b) a else b |
空值与 null 检测
当某个值可以为空时,需要在类型后添加?表示可以为空
此时,声明时不能省略类型
1 | var obj:Objects ? = null; |
类型检测与自动类型转换
is:判断数据是否是某个类型。如果一个不可变的局部变量已经被判断为某个类型,那么检测后的分支中可以直接当作该类型使用,无需显式转换
1 | fun getStringLength(obj: Any): Int? { |
强调为不可变是因为如果可变,那可能会变成其他类型,那就不适用了
for 循环
1 | var items = listof("apple", "banana", ""kiwifruit); |
while 循环
1 | var index = 0; |
when 表达式
1 | private fun testWhen(s: Any?) { |
有点类似 java 中的 switch
区间 range
1 | var x = 6; |
注意:区间只用于数字
集合
集合的迭代可以用的上面的 for in 循环
1 | var items = listof("a", "b", "c"); |
判断集合中是否包含某元素 用 while in || when
1 | while ("a" in items) { |
用 lambda 来过滤和映射集合 ,与 java8 lambda stream 大致
1 | items.filter{ it.indexOf("a") >= 0 } |
协程
什么是协程?
官方原话:协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码。
协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。
协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。
CPU 调度线程是基于时间片轮转算法实现,当线程过多时,每个时间片中切换、挂起线程就占用了接近一半的时间,执行效率非常低。
而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
协程很重要的一点就是当它挂起的时候,它不会阻塞其他线程。协程底层库也是异步处理阻塞任务,但是这些复杂的操作被底层库封装起来,协程代码的程序流是顺序的,不再需要一堆的回调函数,就像同步代码一样,也便于理解、调试和开发。它是可控的,线程的执行和结束是由操作系统调度的,而协程可以手动控制它的执行和结束。
build.gralde 中引入依赖
implementation “org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1”
runBlocking
1 | class MainActivity4 : AppCompatActivity() { |
打印结果如下:
协程执行 0 线程 id:1
协程执行 1 线程 id:1
协程执行 2 线程 id:1
协程执行 3 线程 id:1
协程执行 4 线程 id:1
协程执行 5 线程 id:1
协程执行 6 线程 id:1
协程执行 7 线程 id:1
线程 main :1
我们可以得出结论:
runBlocking 会阻塞线程,但允许还是在当前线程
launch:Job
1 | fun testJob() { |
打印结果如下:
testJob: 线程 main:2
testJob: 线程 id:6809
testJob: 线程 id:6807
testJob: 线程 id:6807
testJob: 线程 id:6815
testJob: 线程 id:6807
testJob: 线程 id:6808
testJob: 线程 id:6818
testJob: 线程 id:6809
说明使用 GlobalScope.launch
- 并不是在单一线程执行的,当多个任务时,系统自动创建线程、分配线程来执行任务
- 是非阻塞、异步执行,相当于开启了子线程
async
1 | fun testAsync(){ |
输出结果:
E/MainActivity4: 线程 main :2
E/MainActivity4: getResult1: ThreadId = 7870
E/MainActivity4: getResult2: ThreadId = 7870
E/MainActivity4: testAsync: ThreadId = 7870 result = 9
其中当打印 getResult2 后就打印了 testAsync: result = 9
阻塞时间为 5 秒,而不是 8 秒,说明 getResult1、getResult2 是并行的。
而且发现 threadId 都一样,说明处于同一个线程。
但是其实并不是一定处于同一个线程,如果多尝试几次,肯定也会出现完全不一样的结果。
- 协程并不等于线程,协程内部会根据任务情况自动调度线程。
launch()有三个入参: 1.协程下上文;2.协程启动模式;3.协程体:block 是一个带接收者的函数字面量,接收者是 CoroutineScope
我们在线程中执行的代码就是第三个参数
协程上下文
上下文可以有很多作用,包括携带参数,拦截协程执行等等,多数情况下我们不需要自己去实现上下文,只需要使用现成的就好。上下文有一个重要的作用就是线程切换,Kotlin 协程使用调度器来确定哪些线程用于协程执行,Kotlin 提供了调度器给我们使用:
Dispatchers.Main:使用这个调度器在 Android 主线程上运行一个协程。可以用来更新 UI 。在 UI 线程中执行
Dispatchers.IO:这个调度器被优化在主线程之外执行磁盘或网络 I/O。在线程池中执行
Dispatchers.Default:这个调度器经过优化,可以在主线程之外执行 cpu 密集型的工作。例如对列表进行排序和解析 JSON。在线程池中执行。
Dispatchers.Unconfined:在调用的线程直接执行。
调度器实现了 CoroutineContext 接口。
我们还可以自己自定义调度器
1 | var dis = Executors.newCachedThreadPool().asCoroutineDispatcher() |
启动模式
在 Kotlin 协程当中,启动模式定义在一个枚举类中:
public enum class CoroutineStart {
DEFAULT,
LAZY,
@ExperimentalCoroutinesApi
ATOMIC,
@ExperimentalCoroutinesApi
UNDISPATCHED;
}
一共定义了 4 种启动模式,
DEFAULT: 默认的模式,立即执行协程体
LAZY: 只有在需要的情况下运行
ATOMIC: 立即执行协程体,但在开始运行之前无法取消
UNDISPATCHED :立即在当前线程执行协程体,直到第一个 suspend 调用
协程体
协程体是一个用 suspend 关键字修饰的一个无参,无返回值的函数类型。被 suspend 修饰的函数称为挂起函数,与之对应的是关键字 resume(恢复)
注意:挂起函数只能在协程中和其他挂起函数中调用,不能在其他地方使用。
关键字
Kotlin 的硬关键宇包括:
as 一一 用于做类型转换或为 import 语句指定别名
as?一一类型安全 的类型转换运算符。
break 一一中断循环
class 一一声明类。
continue 一忽略本次循环剩下的语句,重新开始下一次循环。
do 一一用于 do while 循环
else 一一在 if 分支中使用
false 一一在 Boolean 类型中表示假 的直接量。
for 一一用于 for 循环
fun 一一声 明函数
if-在 if 分支中使用
in 一一在 for 循环中使用; in 还可作为双目运算符,检查 一个值是否处于区间或集合 内;
in 也可 在 when 表达式中使用; in 还可用于修饰泛型参数,表明该泛型参数支持逆变
!in 一一可作为双目运算符 的反义词:!in 也可在 when 表达式中使用
is 一一用于做类型检查(类 Java instanceof) 或在 when 表达式中使用
!is 一一 用于做类型检查( is 的反义词〉或在 when 表达式中使用
null 一一 代表空的直接量。
object ——用于声明对象表达式或定义命名对象
package 一一用于为当 前文件指定包
return 一一声明函数的返回
super 一-用于引用父类实现的方法或属性,或者在子类构造器中调用父类构造器
this 一一 代表当前类的对象或在构造器中调用当前类的其他构造器
throw 一一用于抛出异常
true 一一在 Boolean 类型中表示真的直接量。
try 一一开始异常处理
typealias 一一用于定义类型别名。
val 一声明只读属性或变量。
var 一一声明可变属性或变量。
when 一一用于 when 表达式。while 一一-用于 while 循环或 do while 循环
Kotlin 的软关键宇包括:
by 一一用于将接口或祖先类的实现代理给其他对象。
catch 一一在异常处理中用于捕捉异常
constructor 一一用于声明构造器。
delegate 一用于指定该注解修饰委托属性存储其委托实例的字段
dynamic 一一主要用于在 Kotlin/JavaScript 中引用 一个动态类型
field 一一用于指定该注解修饰属性的幕后字段。
file 一一用于指定该注解修饰该源文件本身
finally 一一异常处理中的 finally
get 一一用于声明属性的 getter 方法,或者用于指定该注解修饰属性的 getter 方法
import 一一用于导包。
init 一一用于声明初始化块
param 一一用于指定该注解修饰构造器参数
property 一一用于指定该注解修饰整个属性(这种目标的注解对 Java 不可见,因为 Java
并没有真正的属性)。
receiveris 一一用于指定该注解修饰扩展方法或扩展属性的接收者
set 一一用于声明属性的 setter 方法,或者用于指定该注解修饰属性的 setter 方法
setparam 一一用于指定该注解修饰 setter 方法的参数
where 一一用于为泛型参数增加限制。
Kotlin 的修饰符关键宇包括:
abstract 一一用于修饰抽象类或抽象成员
annotation 一一用于修饰一个注解类。
companion 一一用于声明一个伴生对象
const 一一用于声明编译时常量
crossinline 一一用于禁止在传给内联函数的 Lambd 表达式中执行非局部返回
data 一一用于声明数据类。
enum 一一用于声明枚举
external 一一用于声明某个方法不由 Kotlin 实现(与 Java 的 native 相似〉。
final 一一用于禁止被重写
infix 一一声明该函数能以双目运算符的格式执行
inline 一一用于声明内联函数, Lambda 表达式可在内联函数中执行局部返回。
inner 一一用于声明内部类,内部类可以访问外部类的实例
internal 一一用于表示被修饰的声明只能在当前模块内可见
lateinit——-用于修饰 non-null 属性,用于指定该属性可在构造器以外的地方
初始化
noinline 一一用于禁止内联函数中个别 Lambda 表达式被内联化
open 一一用于修饰类,表示该类可派生子类;或者用于修饰成员,表示该成员可以被
重写。
out 一一用于修饰泛型参数,表明该泛型参数支持协变。
override 一一用于声明重写父类的成员
private ——private 访问权限
protected ——–protected 访问权限
public——-public 访问权限。
reified 一一用于修饰内联函数中的泛型形参,接下来在该函数中就可像使用普通类型
一样使用该类型参数。
sealed 一一用于声明一个密封类。
suspend 一一用于标识一个函数后 Lambda 表达式可作为暂停。
tailrec 一一用于修饰一个函数可作为尾随递归函数使用。
vararg 一一用于修饰形参,表明该参数是个数可变的形参。