Kotlin是一门年轻的语言,但很显然它与Java有着不同的“纹理”。在撰写本书时,Kotlin官方主页“为什么是Kotlin”( https://oreil.ly/pqZbu )给出了4个设计目标:简明、安全、可互操作和工具友好。Kotlin及其标准库的设计者还对支持这些设计目标的隐式偏好进行了编码。这些偏好包括:
● Kotlin更倾向于对不可变的数据进行转换,而不是修改它们的状态。数据类使定义具有值语义的新类型变得容易,标准库使转换不可变数据的集合比迭代和修改集合的状态更容易、更简洁。
● Kotlin更倾向于将行为明确化。例如,类型之间没有隐含的强制转换,即使是从较小的范围到较大的范围也是如此,Java将int值隐式地转换为long值(因为没有精度损失)。在Kotlin中,必须明确地调用Int.toLong()。当涉及控制流时,对明确性的偏爱尤为强烈。尽管你可以为自己的类型重载算术和比较运算符,但不能重载逻辑运算符(&&和||),因为这将允许你定义不同的控制流。
● Kotlin更倾向于静态而非动态绑定。Kotlin鼓励类型安全的、组合式的编码风格。扩展函数是被静态地绑定的。默认情况下,类是不可扩展的,方法不可以是多态的。你必须显式地声明多态和继承。如果你想使用反射,就必须添加一个特定于平台的库依赖。Kotlin从一开始就被设计为与语言感知IDE一起使用,IDE会静态分析代码以指导程序员,自动导航,以及自动进行程序转换。
● Kotlin不擅长处理特殊情况。与Java相比,Kotlin的特殊场景较少,且交互方式不可预测。Kotlin的原始类型和引用类型之间没有区别。对于无返回值的函数没有void类型,Kotlin中的函数要么返回一个值,要么根本就不返回。扩展函数允许你为现有的类型添加新的操作,这些类型在调用点看起来是一样的。你可以将新的控制结构写成内联函数,并且break、continue和return语句的作用与内建控制结构的作用相同。
● Kotlin打破了自己的规则,使迁移更容易。Kotlin语言有一些特性,允许常用的Java代码和Kotlin代码共存于同一个代码库中。其中一些功能取消了类型检查器提供的保障,它们只应用于与旧版的Java代码交互操作。例如,lateinit在类型系统中开了一个后门,这样,通过反射来初始化对象的Java DI(依赖注入)框架就可以绕过通常由编译器执行的封装边界来注入值。如果你把一个属性声明为lateinit var,那就得确保代码在读取该属性之前对其进行初始化。编译器不会发现这类错误。
当我们(Nat和Duncan)重新审视我们最早用Kotlin编写的代码时,它们往往看起来像是用Kotlin语法写成的Java代码。我们在写了很多年的Java代码之后转到Kotlin,根深蒂固的习惯影响了我们编写Kotlin代码的方式。我们写了不必要的模板,没有充分地利用标准库,并且避免使用null,因为我们还不习惯使用强制空指针安全的类型检查器。我们团队中的Scala程序员更甚,他们的Kotlin代码看起来就像Kotlin扮演的Haskell,并试图成为Scala。我们都还没有找到使用Kotlin的最佳方式。
由于我们需要继续在Java代码上工作,在这种情况下,如何地道地使用Kotlin对我们来说略显复杂。实际上,仅仅学习Kotlin是不够的,我们必须找到Java和Kotlin的不同“纹理”,并在从一门语言逐渐过渡到另一门语言的过程中,对两者都保有敬畏心。