上篇已提(tu)到(cao)Java中的各种坑。习惯了C#的各种特性和语法糖后,再转到Java感觉比较别扭。最后本着反正Java也不是很熟悉,干脆再折腾折腾其他语言的破罐子破摔的心态,逛了一圈JVM语言,最终决定转Kotlin。
牺牲了CE使得Lambda不像Java中那么多的约束。引入类似Ruby代码块的写法(默认it参数),让代码看起来比较好看,虽然我个人不是很喜欢这种默认约定,但是用起来真香。
不同于其他语言,Kotlin里的if else,try catch等都是表达式,我们可以直接这样子写代码:
val y = if (x % 2 == 0) 'even' else 'odd'
val z = try { readFromFile() } catch (ex: IOException) { '' }
val ls = listOf(1, 2, 3)
ls.map { 2 * it } // returns [2, 4, 6]
Int
扩展了个double
方法:val double = fun Int.() = 2 * this
val x = 3.double() // x = 6
下面例子通过切换this
实现了一个类似C#初始化对象的方法:
class Obj(init: Obj.() -> Unit) {
var prop1: Int = 0
var prop2: String = ''
init {
init(this)
}
}
val obj = Obj {
prop1 = 1
prop2 = 'abc'
}
listOf
,mapOf
。to
操作符等final
,但是有open
。Kotlin中Class默认都是不能继承的。需要继承的Class要在声明的地方加上open修饰。另外提一下有个插件叫all-open,专门用来让所有Kotlin的类变为可继承的……
Kotlin不支持可继承的注解。
List
,Map
不能修改其内部存储的元素。需要修改应该用MutableList
和MutableMap
。
号称和Java 100%兼容,但是不能访问Lombok生成的方法!
因为Lombok的方法是编译期通过注解处理器(annotation processing)生成的,Kotlin编译时只调用了Javac,所以无法处理Lombok定义的方法。强制先编译Java代码,后编译Kotlin代码,可以解决这个问题,但是又会有新的问题:你不能在Java代码中调用Kotlin代码。所以如果你要混合使用Java和Kotlin的话,推荐所有数据类型都用Kotlin写。
val
和var
var
就是普通变量。val
相当于const
。平时尽量使用val
,有益身心健康。
Null safety是Kotlin宣传得最多的特性,但是我并没有放在“好用的特性”节中介绍,因为它的坑非常多,以至于我十分怀疑null safety的好处是否能抵消它带来的副作用。
null
值,除非加个问号定义为Nullable类型。Nullable类型取值时,强制check null。如果调用Java代码,默认Java代码都是Nullable。不过从Java来的变量不做check null倒是不会报error,只报warning。如果运行时值为null
的话,仍然会抛NullPointerException
。Kotlin的null safety的特性其实只是一个编译器的特性,通过将null
与其他类型区分开来,在类型检查的时候顺便检查了可能出现的NullPointerException
,但是在运行时非Nullable的变量实际上也是可以放进去null
值的(比如通过反射)。null
值(废话),导致这些类型的变量可能会没有默认值!这是个严重的问题。如果是像Int
,String
这种比较像值的类型(其实也是引用类型)还好,可以有0
,空字符串等默认值。而像自定义的类,这种类型的变量其实是个引用,如果不能默认为null
的话,那么它的默认值的取值只能有这么几种方案:
null
有什么区别?又绕回来了。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的兼容性也挺好的,有精力的同学可以放心品尝。