Kotlin 感觉
2023-10-11 22:57:04

给我感觉,这是一门最小完备的语言,功能实用,无以复减,

语法上主要印象有 6 方面,

声明方式和字面量简化

1
2
3
4
5
6
7
val num = 1_000_000.99_99
val s = """
this is a xxxx,
so that this is a xx,
so that this is not a xx,
so the number is $num
"""

1.使用 val、var 动态声明,编译时自动编译为静态类型,

2.数字字面量可写成 val num = 1_000_000.99_99 方便人类查阅,

3.文段字符可连贯书写,无需 + 拼接,无需 \n 换行,

控制流与中缀表达式

1.控制流可直接作为表达式来返回结果,例如

1
2
3
fun test(): String {
return if(xxx) "a" else "b"
}

2.函数也可直接以表达式方式书写,例如

1
fun test() = if(xxx) "a" else "b"

3.区间可以更直观简便,例如循环的步进可以这样写

1
2
for(i in 0..10) { ... }
for(i in 0..<10) { ... }

4.上述 in....< 等,都是中缀表达式,开发者也可通过 infix 自行定义中缀,

1
2
3
4
5
infix fun Any.to(other: Any) { ... }

fun test() {
print(1 to "s") //等同于 print(1.to("s"))
}

头等公民及其延伸

kotlin 允许函数和变量可以不是类成员,直接定义在文件中,作为顶层函数/变量供全局访问,

如此一来,蝴蝶效应,引发诸多连锁改善,

1.由于函数可以不是类成员,kotlin 的语法索性统一为,不得在参数中为类成员赋值,如此便为赋值运算符用作 “参数默认值” 预留了空间,

反观 java,由于方法总是类成员,延伸出可在方法实参中为 “类字段成员” 赋值的写法,而这种鸡肋用法对后来 “参数默认值” 语法的出现造成阻碍,

1
2
3
4
5
6
7
8
9
//java
void setAdapter(Adapter adpt){ ... }
Adapter adpt;
setAdapter(adpt = new Adapter()); //为类字段成员 adpt 赋值

//kotlin
fun setAdapter(count: Int, adpt: Adapter = Adapter()){ ... }
setAdapter() //参数都沿用默认值
setAdapter(adpt = MyAdapter()) //指定某参数使用自定义值

2.由于函数和变量上升为 “头等公民”,和类一样可以直接在文件中声明,由此延伸出拓展、闭包、内联、委托等,

这拓展即是以顶层函数/变量的形式,对类成员进行拓展。在拓展函数中,可以像在类中一样,通过 this 访问到当前类对象,

1
2
3
fun String.haha(s: String){
this.add(s)
}

这闭包(也即 lambda,个人习惯称之为闭包),可直接用在函数参数中,实现回调,

并且如果闭包是函数唯一的参数,那么调用该函数时,可以省略 ( ),直接写闭包 { },

1
2
3
4
5
6
7
8
9
fun doSomething(action: (String)->Unit) {
action("haha")
}

fun test(){
doSomething {
print(it)
}
}

3.由于闭包在编译时是生成一个匿名对象,如果闭包被用在循环中,会不断生成匿名对象,影响性能,故此参数有闭包的函数,一般会通过 inline 内联来修饰,

如此编译时,不会为闭包生成匿名对象和方法来与目标连接,而是直接将闭包中的内容粘贴到目标位置,即便是循环,性能也和代码是直接写在循环中并无二致,

1
2
3
inline fun doSomething(action: (String)->Unit) {
action("haha")
}

4.既然有拓展,那么除了拓展函数,也可定义拓展属性,

拓展属性除了简化 getter setter 的调用,某种程度上也简化了委托,也即可在 getter setter 中植入想要的委托代码,如此在访问该属性时,能顺带执行这些代码,

1
2
3
4
5
6
7
8
9
var TextView.text: CharSequence
get() = {
print("get $getText()")
return getText()
}
set(value) {
setText(value)
print("set $value")
}

5.那么拓展、闭包、内联一组合,便能形成一些实用的 “作用域函数”,例如 apply 函数,

可见不仅是 T.apply 用到拓展,T.( ) 也用到,这使目标对象例如 Paint( ) 在使用 apply 函数时,得以在闭包 { } 中通过隐式 this 来访问 paint 的属性,例如 color、style 等,

1
2
3
4
5
6
7
8
9
10
11
inline fun <T> T.apply(action: T.()->Unit): T {
action()
return this
}

fun test(){
val paint = Paint().apply {
color = "#aaaaaaa"
style = Stroke
}
}

空安全与可空类型

1.kotlin 通过 ? 操作符彻底解决 java 的 null 安全问题(以下简称 NPE),开发者无需再写 if ( null != x ),也无需使用特定框架来规避对应的 NPE,

2.例如从前在 java 下通过 DataBinding ObservableField 的间接通知来规避 “横竖屏布局的 View NPE”,而这涉及 BindingAdapter、xml 属性绑定等一系列前置工作,

如今 kotlin 下可以直接通过使用 ViewBinding,书写便捷,性能还更高些,

1
binding?.tv.setText(xxx)

3.除了 ?. 跳过空对象调用,还有 ?: 操作符来提供非空默认值,

例如 val b = a ?: “xxx”,如 a 为 null,则表达式返回 ?: 后面值,

4.对应的,kotlin 下类型分为 “可空” 和 “不可空” 两种,可空类型都是 Any? 子类,不可空类型都是 Any 子类。多数情况下会是创建可空类型,然后使用 ? 操作符,

协程和异步代码同步写

1.在 java 中,若想避免主线程被阻塞,通常会切到一个子线程,让子线程去阻塞,

不过想要再切回来,只能通过回调,如此在某些场景下可能引发多层嵌套(回调地狱),

并且如果希望多个任务并发(concurrent)或并行(parallel)执行,且执行后在同一时间汇合、继续下一步,java 线程不是那么好操作,需要手动安排亲和度等细节,

故此 kotlin 下可以使用协程,

2.在协程中,原本需要回调的异步方式,可以同步方式书写,

以下我们在主线程中,通过 MainScope.launch 启动一个协程,其中两个 async 块都是异步执行,但最终返回结果的顺序,与书写顺序一致,符合人类直觉,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun test(){
MainScope().launch {
println("start") //步骤1
val job1 = async(Dispatcher.Default){ //步骤2
delay(1000)
println("job1 done")
"job1_hhhh"
}
val job2 = async(Dispatcher.Default){ //步骤3
delay(1000)
println("job1 done")
"job2_hhhh"
}
printlin("end ${job1.await()} and ${job2.await()}") //步骤4
}
}

//打印结果:
//start
//job1 done
//job2 done
//end job1_hhhh and job2_hhhh

3.“IO 密集型” 和 “计算密集型” 任务,分别使用 Dispatcher.IO 和 Dispatcher.Default 调度模式,

其中 Dispatcher.Default 所用线程池,是根据 CPU 核心数配置的线程,充分利用多核心实现并行计算,无需开发者手动配置线程池、安排亲和度,

响应式流开发模式

如果只能用一张图形容 “对 kotlin 的感觉”,笔者认为 flow 可担此任,

1
2
3
fun test() = flow {
emit(Sealed)
}.flowOn(IO)

如上,伪代码中涵盖了 flow、sealed class、协程调度器等元素,示意 “在子线程中处理任务,并在过程中发射 ‘密封类结果’ 到主线程观察者” 的响应式开发模式,

软件形形色色,无非 “数据来,数据去”,语言则是实现这一切的工具,

从这角度来看,响应式开发、单向数据流,浑然天成、理所当然 —— 软件开发本就如此应当,

最后

kotlin 是 java IDE 厂商 jetbrain 打磨 12 年之作,通过语法糖实现了诸多 “实用、且原本在 java 中需要手写乃至成本高昂” 的特性,所以 kotlin 可以说是对 java 及其设计模式的最佳实践,

有人说 kotlin 是 java pro max,笔者认为 kotlin 是语法最小完备的现代化语言,功能实用,无以复减,

目前唯一遗憾是 jvm 内存占用,在 jetbrain IDE 眼里,内存跟不要钱似的;在苹果眼里,内存比黄金还贵,而运行在 jvm 上的 kotlin,也是 400MB 起步,在 8GB 丐版 mac 下也就能跑 10 来个,

期待 jetbrain 对 kotlin/native 的持续发力,使让 macOS 等桌面操作系统也能用上内存只占 60-70MB 的软件,