Kotlin 现在很流行,它提供了编译时 null 安全,代码更加简洁。它比 Java 更好,你应该切换到 Kotlin,否则就只能坐以待毙。不过,在转向 Kotlin 之前,请先听听这个故事——在这个故事里,那些稀奇古怪的东西让我们忍无可忍,最后不得不使用 Java 重写整个项目。
我们尝试过 Kotlin,但现在开始使用 Java 10 重写代码。
我有一组自己最喜欢的 JVM 语言,/main 目录下的 Java 代码和 /test 目录下的 Groovy 代码是我最爱的组合。2017 年夏天,我的团队开始了一个新的微服务项目,和往常一样,我们讨论了要使用什么编程语言和技术。我们想尝试新的东西,所以决定试试 Kotlin。由于在 Kotlin 中找不到可替代 Spock 的测试框架,所以我们决定继续在 /test 目录中使用 Groovy(Spek 不如 Spock 好)。2018 年冬天,在使用 Kotlin 数月之后,我们总结了它的优势和劣势,并得出结论:Kotlin 导致我们生产力下降。于是,我们开始使用 Java 重写这个微服务。
原因如下:
命名遮蔽(name shadowing)
类型推断
编译时 null 安全
类字面量
反向类型声明
Companion 对象
集合字面量
Maybe 语法
数据类
公开类
陡峭的学习曲线
命名遮蔽
Kotlin 的命名遮蔽对我来说是个最大的惊喜。比如下面这个函数:
fun inc(num : Int) {
val num = 2
if (num > 0) {
val num = 3
}
println ("num: " + num)
}
当你调用 inc(1) 时会打印出什么?在 Kotlin 里,方法参数是按值传递,所以我们不能修改 num 参数。这样的设计是对的,因为方法参数本来就不应该被修改。不过,我们可以用相同的名字定义另一个变量,并将它初始化为任何想要的值。现在,在方法作用域内有两个名为 num 的变量。当然,现在一次只能访问一个 num 变量。所以从根本上说,num 的值被改变了。
我们还可以在 if 代码块中添加另一个 num(新的代码块作用域)。
在 Kotlin 中,调用 inc(1) 时会打印出 2,而在 Java 中,等效代码无法通过编译:
void inc(int num) {
int num = 2; //error: variable 'num' is already defined in the scope
if (num > 0) {
int num = 3; //error: variable 'num' is already defined in the scope
}
System.out.println ("num: " + num);
}
命名遮蔽并非 Kotlin 独有,它在编程语言中是很常见的。在 Java 中,我们习惯用方法参数来遮蔽类字段:
public class Shadow {
int val;
public Shadow(int val) {
this.val = val;
}
}
Kotlin 中的命名遮蔽做得有点过了,这绝对是 Kotlin 团队的一个设计缺陷。IDEA 团队试图通过为每个被遮蔽的变量显示警告(“Name shadowed”)来解决此问题。两个团队都属于同一家公司,或许他们可以就遮蔽问题达成共识?我认为,IDEA 团队是对的,因为我想象不出遮蔽方法参数有什么用处。
类型推断
在 Kotlin 中,在使用 var 或 val 声明变量时,通常会让编译器根据右边的表达式猜出变量类型。我们称之为局部变量类型推断,这对程序员来说是一个很大的改进,我们因此可以在不影响静态类型检查的情况下简化代码。
例如,这行 Kotlin 代码:
var a = "10"
将由 Kotlin 编译器翻译成:
var a : String = "10"
这是 Kotlin 曾经比 Java 真正好的地方。我故意说“曾经”,那是因为 Java 10 现在也有了局部变量类型推断。
Java 10 中的类型推断:
var a = "10";
为了公平起见,我需要补充一点,Kotlin 在这方面仍然略胜一筹,因为在 Kotlin 中,可以在其他上下文中使用类型推断,例如,单行代码方法。
编译时 null 安全
null 安全类型是 Kotlin 的杀手级特性。在 Kotlin 中,类型默认是不可空的。如果你需要一个可空类型,需要添加?,例如:
val a: String? = null // ok
val b: String = null // compilation error
如果使用不带空值检查的可空变量,将无法通过编译,例如:
println (a.length) // compilation error
println (a?.length) // fine, prints null
println (a?.length ?: 0) // fine, prints 0
一旦使用了这两种类型,不可空的 T 和可空的 T?,那么就可以避免出现 Java 中最常见的异常——NullPointerException。真的吗?事情并没有那么简单。
文章评论 本文章有个评论