Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的 Swift,由 JetBrains 设计开发并开源。
Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。
用了 Kotlin 就不想回到 Java,本文主要记录一些高级用法
更多细节可查看:基本语法 - Kotlin 语言中文站 (kotlincn.net)
基本知识
区间
- 左闭右闭:
0..10
- 左闭右开:
0 unitil 10 (step 2)
- 降序(左闭右闭):
10 downto 1
类
主构造函数
1 | class Examle(...){ |
次构造函数
不常用,一般直接指定默认值
1 | class Examle(...){ |
修饰符
public
:(默认)对所有类可见private
:对当前类内部可见protected
:对当前类和子类可见internal
:对同一模块的类可见
数据类和单例类
- 数据类:
data class
,等同于 Java 中一长串的 bean - 单例类:
object
,等同于 Java 中的单例模式(全局至多只有一个实例)
Lambda
简而言之,就是可以作为参数传递的代码。
太常用也太多了,写写就会了
标准函数
只介绍最常用的 3 个
With
一个非扩展函数:上下文对象作为参数传递,但是在 lambda 表达式内部,它可以作为接收者(this
)使用。 返回值是 lambda 表达式结果。
接收 2 个参数:
- 任意类型的对象
- Lambda 表达式
1 | val result = with(obj){ |
Run
上下文对象 作为接收者(this
)来访问。 返回值 是 lambda 表达式结果。
run
和 with
做同样的事情,但是调用方式和 let
一样——作为上下文对象的扩展函数.
当 lambda 表达式同时包含对象初始化和返回值的计算时,run
很有用。
1 | val result = service.run { |
Apply
上下文对象 作为接收者(this
)来访问。 返回值 是上下文对象本身。
对于不返回值且主要在接收者(this
)对象的成员上运行的代码块使用 apply
。apply
的常见情况是对象配置。这样的调用可以理解为“将以下赋值操作应用于对象”。
1 | val adam = Person("Adam").apply { |
函数选择
为了选择合适的作用域函数,它们之间的主要区别表。
函数 | 对象引用 | 返回值 | 是否是扩展函数 |
---|---|---|---|
let |
it |
Lambda 表达式结果 | 是 |
run |
this |
Lambda 表达式结果 | 是 |
run |
- | Lambda 表达式结果 | 不是:调用无需上下文对象 |
with |
this |
Lambda 表达式结果 | 不是:把上下文对象当做参数 |
apply |
this |
上下文对象 | 是 |
also |
it |
上下文对象 | 是 |
拓展函数和运算符重载
拓展函数
基本格式:
1 | fun ClassName.methodName(pararm: Int):Int{ |
例子:
1 | fun String.showToast(duration: Int = Toast.LENGTH_SHORT) { |
运算符重载
例子:
1 | class Money(val value:Int){ |
高阶函数和内联函数
高阶函数
如果一个函数接收另一个函数作为参数,或者韩非子的类型是另一个函数,则称之为高阶函数。
基本规则:() -> Unit
例子如下,相当于能嵌套函数
1 | fun CardItem( |
内联函数
高阶函数实现的 Lambda 表达式在底层被转换为匿名类,每次调用都会创建一个新的匿名类实例,造成额外开销
函数前添加 inline
可消除开销:inline fun example(){}
泛型和委托
泛型
一般编程模式下,需要给任何一个变量指定具体的类型,而泛型允许我们在不指定具体类型的情况下进行编程,这样的代码会有更好的拓展性。
泛型类:
1 | class MyClass<T>{ |
泛型方法:
1 | class MyClass{ |
范围:
可通过 <T : Number>
的形式指定范围,此时只允许数字类型,字符串会报错
默认类型可空,即 Any?
,不想为空可改为 Any
委托
委托是一种设计模式,操作对象自己不会去处理某段逻辑,而是把工作委托给另外一个辅助对象处理。
类委托:
借助委托可以轻松自己实现类,以下通过 Hashset
自定义一个类:by
是实现委托的关键字
1 | class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet{ |
属性委托:
将一个属性(字段)的具体实现委托给另一个类去完成
1 | class MyClass{ |
泛型实化
指定泛型的实际类型
inline fun <reified T> start(block: Intent.() -> Unit = {}){}
此时就可以得到 T::class.java
类型:
1 | val intent = Intent(context, T::class.java) |
泛型协变和逆变
Todo
协程
可以理解为轻量级的线程,可以在单线程中模拟多线程效果,与线程不同点在于:
- 线程依靠操作系统调度实现不同线程切换
- 协程在编程语言层面实现不同协程切换,大大提高并发编程的运行效率
特点:协程是我们在 Android 上进行异步编程的推荐解决方案。值得关注的特点包括:
- 轻量:可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
- 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
- 内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
- Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。
作用域构建器
Tips:
delay()
:让当前协程延迟指定时间再运行,会阻塞挂起函数,只会挂起当前协程,不会影响其他协程执行suspend
:将任意函数声明为挂起函数,挂起函数间可以互相调用
作用域
GlobalScope.launch{}
:顶层协程(不建议用)runBlocking{}
:作用域内所有代码和子协程没有全部执行完之前一直阻塞当前线程(测试用,生产可能有性能问题)launch
:在作用域内创建多个协程coroutineScope{}
:继承外部协程作用域并创建一个子协程,配合suspend
使用,和runBlocking
类似,用于生产环境
1 | suspend fun printDot() = coroutineScope{ |
常用写法
1 | val job = Job() |
获取执行结果
调用 async
后,代码立即执行,如果调用 await()
时还没执行完,则会阻塞当前协程,直到获得 async
执行结果
1 | runBlocking{ |
withContext
是一个挂起函数,可以理解为 async
的一种简化版写法:
1 | runBlocking{ |
线程参数
除了 coroutineScope
,其他函数都可以指定线程参数,withContext
必须,其他可选
Dispatchers.Default
:默认低并发线程策略Dispatchers.IO
:较高并发的线程策略Dispatchers.Main
:不会开启子线程,在 Android 主线程执行(只在安卓中使用)
Android 中要求网络请求必须在线程执行,定义协程也不行