给我感觉,这是一门最小完备的语言,功能实用,无以复减,
语法上主要印象有 6 方面,
声明方式和字面量简化
1 | val num = 1_000_000.99_99 |
1.使用 val、var 动态声明,编译时自动编译为静态类型,
2.数字字面量可写成 val num = 1_000_000.99_99
方便人类查阅,
3.文段字符可连贯书写,无需 +
拼接,无需 \n
换行,
控制流与中缀表达式
1.控制流可直接作为表达式来返回结果,例如
1 | fun test(): String { |
2.函数也可直接以表达式方式书写,例如
1 | fun test() = if(xxx) "a" else "b" |
3.区间可以更直观简便,例如循环的步进可以这样写
1 | for(i in 0..10) { ... } |
4.上述 in
、 ..
、 ..<
等,都是中缀表达式,开发者也可通过 infix 自行定义中缀,
1 | infix fun Any.to(other: Any) { ... } |
头等公民及其延伸
kotlin 允许函数和变量可以不是类成员,直接定义在文件中,作为顶层函数/变量供全局访问,
如此一来,蝴蝶效应,引发诸多连锁改善,
1.由于函数可以不是类成员,kotlin 的语法索性统一为,不得在参数中为类成员赋值,如此便为赋值运算符用作 “参数默认值” 预留了空间,
反观 java,由于方法总是类成员,延伸出可在方法实参中为 “类字段成员” 赋值的写法,而这种鸡肋用法对后来 “参数默认值” 语法的出现造成阻碍,
1 | //java |
2.由于函数和变量上升为 “头等公民”,和类一样可以直接在文件中声明,由此延伸出拓展、闭包、内联、委托等,
这拓展即是以顶层函数/变量的形式,对类成员进行拓展。在拓展函数中,可以像在类中一样,通过 this 访问到当前类对象,
1 | fun String.haha(s: String){ |
这闭包(也即 lambda,个人习惯称之为闭包),可直接用在函数参数中,实现回调,
并且如果闭包是函数唯一的参数,那么调用该函数时,可以省略 ( ),直接写闭包 { },
1 | fun doSomething(action: (String)->Unit) { |
3.由于闭包在编译时是生成一个匿名对象,如果闭包被用在循环中,会不断生成匿名对象,影响性能,故此参数有闭包的函数,一般会通过 inline 内联来修饰,
如此编译时,不会为闭包生成匿名对象和方法来与目标连接,而是直接将闭包中的内容粘贴到目标位置,即便是循环,性能也和代码是直接写在循环中并无二致,
1 | inline fun doSomething(action: (String)->Unit) { |
4.既然有拓展,那么除了拓展函数,也可定义拓展属性,
拓展属性除了简化 getter setter 的调用,某种程度上也简化了委托,也即可在 getter setter 中植入想要的委托代码,如此在访问该属性时,能顺带执行这些代码,
1 | var TextView.text: CharSequence |
5.那么拓展、闭包、内联一组合,便能形成一些实用的 “作用域函数”,例如 apply 函数,
可见不仅是 T.apply 用到拓展,T.( ) 也用到,这使目标对象例如 Paint( ) 在使用 apply 函数时,得以在闭包 { } 中通过隐式 this 来访问 paint 的属性,例如 color、style 等,
1 | inline fun <T> T.apply(action: T.()->Unit): T { |
空安全与可空类型
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 | fun test(){ |
3.“IO 密集型” 和 “计算密集型” 任务,分别使用 Dispatcher.IO 和 Dispatcher.Default 调度模式,
其中 Dispatcher.Default 所用线程池,是根据 CPU 核心数配置的线程,充分利用多核心实现并行计算,无需开发者手动配置线程池、安排亲和度,
响应式流开发模式
如果只能用一张图形容 “对 kotlin 的感觉”,笔者认为 flow 可担此任,
1 | fun test() = flow { |
如上,伪代码中涵盖了 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 的软件,