boolean tuesday = DateUtils.isTuesday(date);
这里我增加一个十分有用的 “isTuesday” 函数给我们的 date utils,然后我们用传统的静态方法调用它。在我展现 Kotlin 语法前,我想和 C# 做个比较。这是在 C# 中我们需要在 date 类中添加一个函数的实现, DateTime:
[代码]java代码:
1
2
3
4
static boolean IsTuesday(this DateTime date)
{
return date.DayOfWeek == DayOfWeek.Tuesday;
}
这里我们得到 date 的实例,然后调用这个方法,在任何 .NET 环境下这都能行得通,只要你在某处定义了该扩展方法,你能在你的整个项目中都能引用到 DateTime。我接下来会解释一个有意思的语言特征。这是 Kotlin 定义的方法:
[代码]csharp代码:
1
2
3
4
5
fun Date.isTuesday(): Boolean {
return getDay() == 2
}
val tuesday = date.isTuesday();
在 Kotlin 中,我们用我们想增加的函数的方法的类型来给原有的函数名增加了一个前缀。我们现在调用 Date.IsTuesday 而不是 isTuesday。然后你能在最后得到返回值。我们最后能调用 “getDay” 并且这个扩展的方法能够被调到,尽管我们没有使用实例来调用它。我们的调用方式和该类原来就有有这个方法时调用的方式一样。我们也能够在 Date 上调用其他的方法。
Kotlin 的一个非常好的功能是,它会自动地转换有 getters 和 setters 综合属性的类型。所以我能够替换 getDay() 为 day,因为这个 day 的属性是存在的。它看起来像一个 field,但是实际上是个 property – getter 和 setter 的概念融合在了一起。
前面我指出的单行函数表达式会使得语法变得更简洁,所以我们可以把上面的代码修改成下面的样子,并且隐藏返回值:
[代码]csharp代码:
1
fun Date.isTuesday() = day == 2
现在我们有一个非常漂亮的单行实现的并且在 date 上使用的扩展方法了。
不像 C#,如果我们不在同一个 package 里面的话,扩展函数需要显示引用。如果不是同一个文件里,我们需要非常清楚地描述这个函数从何而来: import com.example.utils.isTuesday
这和 Java 的静态方法 import 非常类似。我们需要显示声明我们调用的函数,这样函数的来源就不会模糊。因为当我看到这段代码时,作为一个 Java 开发者,我知道 date 没有 “isTuesday” 的函数,但是显式的 import 告诉我它来自于公共的某个 util package。而在 C# 中,我们不知道这个扩展函数从何而来。它可能来自一个库,你的源代码,或者其他的地方,你无法静态地知道,除非你到 IDE 里面找到它的定义。
当然,你可以 command B 然后进入这个函数,这看起来像是 date 类型的一个方法。这和我们在 Java 里面编写它,然后产生的二进制代码一模一样。而且,因为它关注 inter-op,你可以从 Java 侧使用一个自动生成类来调用它。
类型系统中的 Null
我提过 null 会是个问题。Kotlin 事实上在它的类型系统里重新表述了 null。对于 get string 可能返回 null 的函数来说,我会返回 String? 来表述这可能是个 null 值。然后在 get string 函数中,我使用 double exclamation mark 语法来直接调用这个 null。这基本上是说,“我知道这可能是个 null,所以把它变成一个普通字符串。” 当它真是 null 的时候,它会发出一个检查的信号,然后抛出异常。
但是在消费者的代码里面,代码往往是直接调用该函数,类型系统会生成一个带有问号的字符串,并且传递到调用者的代码处。这意味着如果你不首先做 null 检查或者提前的默认处理机制,你就永远不能解引用。这样,它会最终解决消费者代码中导致 null 指针异常的问题。
函数表达式入门
另外,函数表达式也被称作 lambdas 或者 closures。这里有一个最简单的函数表达式: { it.toString() }。它是一段代码在 “it” 变量上调用了 two-string 函数。“it” 是个 built-in 的名字。当你在写这些函数表达式的时候,如果你只有一个参数传入这段代码,你可以用 “it” 引用,这只是一个你不需要定义参数的方法。