Kotlin的语法糖们

当今的Kotlin真是炙手可热啊。自2011年JetBrains为了提高IDEA的销量而推出这个项目至今,它一直低调而稳定地发展着。到了上个月即2017年5月,Google宣布将Kotlin语言作为Android开发的一级编程语言,算是到达了语言生的第一个巅峰。Kotlin自身受到Java、C#、JavaScript、Scala、Groovy等语言的影响,本文总结了它提供的部分常见语法糖并与其它语言进行比较。

字符串模板(string template

Kotlin可以直接通过println("Hello, {name}")println("Hello, $name")来使用字符串模板,而Java则需要先借助String.format来生成字符串。而kotlin还支持表达式,如${person.name}${2 * 3}。这块古老的糖从shell开始就有了:

1
echo "Hello, ${name}"

如果你疑惑为什么无须导入包即可直接使用println,那是因为这个方法所在的kotlin.io包是默认导入(Default Imports)的,正如Java会默认导入java.lang.*一样。Kotlin的默认导入请参考这里

分号推断(semicolon inference

我们知道Java中的每一条语句结束后,需要加上分号。Kotlin中的分号是可选的。这应该是来自JavaScript吧。不过写JavaScript还是推荐把分号都加上,否则可能有危险。Groovy、Scala也有同样的能力。

定义函数(define function

JavaScript中,function;Go语言,func;Kotlin,fun。怎么看都像是SIM卡变成了micro-SIM,然后又变成了nano-SIM卡。Kotlin可以在类之外定义全局函数,也可以在函数中定义局部函数。这一点类似于JavaScript。

var/val(local variable

变量是var,表示variable;常量是val,表示fixed value。这个很明显来自Scala。在Java中就只能用final关键字了。

可空值(nullable value

在变量类型后面加上一个问号,表明这个变量可以为null,否则默认不能为null。例如:
这一句编译错误:var a: Int = null
这样才能成功:var a: Int? = null
这块糖显然来自于C#,但是更加严格了。它是为了避免Java中的null所造成的十亿美元的错误

集合字面量(collection literals

Java从来就不愿意用糖吸引小朋友……所以一般快速创建集合是这么写的:

1
List colors = Arrays.asList("red", "blue");

有好事者(Guava)提供了这样的类库:

1
List colors = ImmutableList.of("red", "blue");

其实Java 8提供的Stream也许已经能够满足你的需求:

1
Stream colors = Stream.of("red", "blue");

Java 9很可能会引入集合字面量

1
List colors = List.of("red", "blue");

Kotlin出手了:

1
val colors = listOf("red", "blue")

不过实际上并非是语法层面的糖,listOf只是一个方法而已。我相信是从Scala中偷师的:

1
val colors = List("red", "blue")

当然Scala还有自己的经典方式:

1
val colors = "red" :: "blue" :: Nil

C#可以这样来初始化集合:

1
var colors = new ArrayList{"red", "blue"};

JavaScript、Ruby、Go等直接扩展了数组,所以就更省心了。

when表达式(when expression

when表达式有点像是不需要breakswitch…case

1
2
3
4
5
6
7
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 这里可以写多行哦
print("x is neither 1 nor 2")
}
}

但它还可以做得更多:

1
2
3
4
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")
}

可以把它作为扩展版if…else来用。它有点类似Groovy的switch

is运算符(is operator

先看代码:

1
2
3
4
5
6
7
fun getStringLength(obj: Any): Int? {
if (obj is String && obj.length > 0) {
return obj.length
}

return null
}

在这里,obj is String之后就可以将其作为String来使用了,调用Stringlength属性。is不仅比instanceof更加短小精悍,而且还会自动将变量转换为is后面的类型。应该是出于C#而胜于C#。C#提供了isas,但是as在Kotlin中显然就毫无用武之地了。

范围运算符(range

Java如果要循环打印1至9,一般就是这样:

1
2
3
4
5
6
7
8
9
// Java 8以前
for (int i = 1; i 运算符重载([operator overloading](https://kotlinlang.org/docs/reference/operator-overloading.html))
这个是C#和Scala的把戏:

```kotlin
data class Point(val x: Int, val y: Int)
operator fun Point.plus(val point: Point) = Point(x + point.x, y + point.y)

Point(1, 2) + Point(3, 4) // Point(x=4, y=6)

怎么知道+号上映射的是plus方法呢?还是得参考官方文档

包别名(package alias

Kotlin支持为包指定别名,对代码洁癖患者可能会起到一定的疗效:

1
import java.math.BigDecimal as bd

Python、Groovy也都是这样:

1
2
import java.util.List as UtilList
import java.awt.List as AwtList

类型别名(type alias

Kotlin还支持为类型指定别名:

1
typealias Row = List

这应该是来自Scala的Type:

1
type Row = List[Int]

扩展函数(extension function

扩展函数允许为一个类增加公有静态方法,调用时就好像这个方法是原生的一样。

1
2
3
4
5
fun String.greetingsWith(greeting: String) {
println("$greeting, $this")
}

"ggg".greetingsWith("Hello")

虽然JavaScript也能轻易做到,但我还是强烈地认为它来自于C#,毕竟都是一脉相承。kotlin更近一步支持扩展属性:

1
2
3
4
val String.halfLength: Int
get() = length / 2

"ggg".halfLength // 1

函数扩展(function expansion

如果函数的最后一个参数类型是个函数,可以通过大括号来传值:

1
2
3
4
5
fun calculate(a: Int, cal: (Int) -> Int): Int {
return cal(a)
}

calculate(3) { x -> x * x } // 9

这应该是来自ruby的block:

1
2
3
4
5
def calculate(a, &cal)
cal.call a
end

calculate(3) { |x| x * x } # 9

默认参数(default arguments

Kotlin支持为参数指定一个默认值:

1
2
3
4
5
6
7
8
fun main(args: Array) {
show()
show("Good morning")
}

fun show (msg: String = "Hello World") {
println("$msg")
}

Java一般是靠重载来实现默认参数。许多其它的语言都支持默认参数。例如,JavaScript在ES6上也支持默认参数了:

1
2
3
function multiply(a, b = 1) {
return a * b;
}

单例对象(singleton

1
2
object Document {
}

这应该是来自于Scala,连关键字都一模一样。

伴生对象(companion object

伴生对象经常用于Factory。这一点相信也是来自于Scala。

1
2
3
4
5
class MyClass {
companion object Factory { // Factory可省略,省略后可用Companion引用
fun create(): MyClass = MyClass()
}
}

getter和setter(getter and setter

Kotlin像C#一样支持属性(property)。我们来看两个例子:

1
2
3
4
5
6
7
val isEmpty: Boolean
get() = this.size == 0

var counter = 0
private set(value) {
if (value >= 0) field = value
}

数据类(data class

告别繁琐的Java数据类的时代到来了:

1
data class User(val name: String, val age: Int)

看到最前面的data,我突然觉得这是源于lombok

没有原始类型(no raw types

Kotlin没有原始类型,一切皆对象:1.inc()。这一点与Ruby、Scala相同。

没有受检查异常(no checked exception

Kotlin并没有受检查异常。因为在大型的项目中,它对代码质量的提升极其有限,但是却大大降低了效率。C#十多年前就是这样了,有兴趣的读者可以参考这篇文章

没有static(no static member

Kotlin也没有static的成员。这一点与Scala相同。在Kotiln中可以使用包级别的函数,或者是伴生对象来实现static的效果。

多返回值(multiple return values

可以从这一个例子中看到Kotlin是如何实现函数的多返回值的。我猜应该是受到了Go语言的启发。

嵌套块注释(nested block comment

Kotlin支持块注释/* … */的嵌套。这可能是来自Haskell或F#。

在线版IDE

如果不想安装那么多语言,可以试试这个在线版IDE,它支持包括Kotlin在内的几十种语言。