类似C++,默认创建一个空对象:但是并非所有类都有默认构造函数,而且在拥有GC的语言中,创建空对象需要分配内存,还会调用构造函数中的逻辑。声明变量时引入这么多过程是非常不合适的。
所以,Kotlin最终选了一种简单粗暴的方案:禁止变量未初始化。
禁止变量未初始化的问题在于,当你需要定义大量的数据类的时候,你就知道有多蛋疼了——所有属性都必须有个初始值。这不仅需要多敲不少键盘,影响手指健康,当碰到属性是非Nullable的聚合时,也常常无法确定其初始值。我已经隐隐看到某些开发人员将所有变量都标记为Nullable的画面了……Kotlin自身也发现了这个问题,因此引入了lateinit特性,然而用起来仍然有点令人胆战心惊。
反序列化。即使是业务逻辑上明确了不会为null值的属性,你也无法保证网络上/数据库里传输过来的数据中,对应的属性会不会是null值,或者干脆漏了,所以就算model设计正确的,实际运行时可能还是会出现NullPointerException。我又隐约看到某些开发人员将所有变量都标记为Nullable的画面了……另外反序列化时,需要先生成一个空对象,也就是属性都没初始化的对象。当然Kotlin不会允许这么做的,所以还需要引入NoArg插件来自动生成无参数的构造函数……
类型擦除式泛型为了和Java 100%兼容,Kotlin不得不跟着Java用类型擦除式泛型,也拥有了前面说过的类型擦除式泛型的所有坑。不过Kotlin可以使用内联函数来稍微缓解类型擦除的负面影响。比如可以这样定义json反序列化的方法:
inline fun <reified T> parse(json: String): T = objectMapper.readValue(json, T::class.java)
Kotlin有两种方法定义一个匿名函数:lambda和anonymous function。当在这两种方法的函数体中使用return时,执行的语义是不同的。根据官方文档return会跳出最近的显示声明的函数或anonymous function。例如下面的return会直接跳出foo函数。
fun foo() { listOf(1, 2, 3, 4, 5).forEach { if (it == 3) return // non-local return directly to the caller of foo() print(it) } println("this point is unreachable") } // outputs: 12而下面这个只是当value == 3时跳过一次循环,相当于其他语言的continue
fun foo() { listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) { if (value == 3) return // local return to the caller of the anonymous fun, i.e. the forEach loop print(value) }) print(" done with anonymous function") } // outputs: 1245 done with implicit label或者也可以使用Label来指定执行return后跳到的位置(感觉像goto似的)。
fun foo() { listOf(1, 2, 3, 4, 5).forEach lit@{ if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop print(it) } print(" done with explicit label") }另外,break和continue也是有类似的问题。
写在最后最近家庭工作都比较忙,这短短的一篇转型踩坑记竟然写了个跨年。有些踩坑的记忆随着时间流逝以及用习惯了给慢慢淡化掉了,于是也没写进来。目前Java系这边的开发我尽量使用Kotlin,并没有碰到什么根本上的大问题,与Java的兼容性也挺好的,有精力的同学可以放心品尝。