通常,这有助于避免拼写错误。
Kotlin类默认为final的。它提倡将继承作为一种有意识的设计选择。这在Spring AOP中是行不通的,但也不难弥补。我们需要将相关类标记为open —— Kotlin的非final关键字。
IntelliJ会给你一个友好的警告。
你可以通过使用maven插件all open来解决这个问题。这个插件可以open带有特定注解的类。更简单的方法是将类标记为open。
Kotlin严格执行null检查。它要求初始化所有标记为不可空的属性。它们可以在声明时或构造函数中初始化。这与依赖注入相反——依赖注入在运行时填充属性。
lateinit修饰符允许你指定属性将在使用之前被初始化。在下面的代码片段中,Kotlin相信config对象将在首次使用之前被初始化。
虽然lateinit对于自动装配很有用,但我建议谨慎地使用它。另一方面,它会关闭属性上的编译时空检查。如果在第一次使用时是null仍然会出现运行时错误,但是会丢失很多编译时空检查。
构造函数注入可以作为一种替代方法。这与Spring DI可以很好地配合,并消除了许多混乱。例如:
这是Kotlin引导你遵循最佳实践的一个很好的例子。
Hibernate和Kotlin可以很好地搭配使用,不需要做大的修改。一个典型的实体类如下所示:
在上面的代码片段中,我们利用了几个Kotlin特性:
属性
通过使用属性语法,我们就不必显式地定义getter和setter了。这减少了混乱,使我们能够专注于数据模型。
类型推断
在我们可以提供初始值的情况下,我们可以跳过类型规范,因为它可以被推断出来。例如:
modelNumber属性会被推断为String类型。
表达式
如果我们稍微仔细地看下toString()方法,就会发现它有与Java有一些不同:
它没有返回语句。这里,我们使用了Kotlin表达式。对于返回单个表达式的函数,我们可以省略花括号,通过等号赋值。
字符串模板
在这里,我们可以更自然地使用模板。Kotlin允许在任何字符串中嵌入${表达式}。这消除了笨拙的连接或对String.format等外部辅助程序的依赖。
相等测试
在equals方法中,你可能已经注意到了这个表达式:
它用==符号比较两个字符串。在Java中,这是一个长期存在的问题,它将字符串视为相等测试的特殊情况。Kotlin最终修复了这个问题,始终把==用于结构相等测试(Java中的equals())。把===用于引用相等检查。
数据类
Kotlin还提供一种特殊类型的类,称为数据类。当类的主要目的是保存数据时,这些类就特别适合。数据类会自动生成equals()、hashCode()和toString()方法,进一步减少了样板文件。
有了数据类,我们的最后一个示例就可以改成:
这两个属性都作为构造函数的参数传入。equals、hashCode和toString是由数据类提供的。
但是,数据类不提供默认构造函数。这是对于Hibernate而言是个问题,它使用默认构造函数来创建实体对象。这里,我们可以利用kotlin-jpa插件,它为JPA实体类生成额外的零参数构造函数。
在JVM语言领域,Kotlin的与众不同之处在于,它不仅关注工程的优雅性,而且解决了现实世界中的问题。
解决Java中的NPE是Kotlin的主要目标之一。将Kotlin引入项目时,显式空检查是最明显的变化。
Kotlin通过引入一些新的操作符解决了空值安全问题。Kotlin的?操作符就提供了空安全调用,例如:
只有当car对象不为空时,才会读取model属性。如果car为空,model计算为空。注意model的类型是Model?——表示结果可以为空。此时,流分析就开始起作用了,我们可以在任何使用model 变量的代码中进行NPE编译时检查。
这也可以用于链式调用:
下面是等价的Java代码:
一个大型的代码库会省掉许多这样的null检查。编译时安全自动地完成这些检查可以节省大量的开发时间。
在表达式求值为空的情况下,可以使用Elvis操作符( ?: )提供默认值:
val year = car?.model?.year ?: 1990
在上面的代码片段中,如果year最终为null,则使用值1990。如果左边的表达式为空,则?: 操作符取右边的值。
Kotlin以Java 8的功能为基础构建,并提供了一等函数。一等函数可以存储在变量/数据结构中并传递出去。例如,在Java中,我们可以返回函数:
Kotlin让这个过程变得更加自然,让我们可以清晰地表达意图:
我们很容易调用fn1及结果函数:
输出
25.0
虽然以上功能在Java中也可以实现,但并不直接,并且包含样板代码。
提供这些功能是为了鼓励团队尝试FP概念,开发出更符合要求的代码,从而得到更稳定的产品。
注意,Kotlin和Java的lambda语法略有不同。这在早期可能会给开发人员带来烦恼。
Java代码:
等价的Kotlin代码:
随着时间的推移,情况就变得明显了,Kotlin支持的应用场景需要修改后的语法。
Kotlin最被低估的优点之一是它可以减少项目中的文件数量。Kotlin文件可以包含多个/混合类声明、函数和枚举类等其他结构。这提供了许多Java没有提供的可能性。另一方面,它提供了一种新的选择——组织类和函数的正确方法是什么?
在《代码整洁之道》一书中,Robert C Martin打了报纸的比方。好代码应该读起来和报纸一样——高级结构在文件上部,越往下面越详细。这个文件应该讲述一个紧凑的故事。Kotlin的代码布局从这个比喻中可见一斑。
建议是——把相似的东西放在一起——放在更大的上下文里。
虽然Kotlin不会阻止你放弃“结构(structure)”,但这样做会使后续的代码导航变得困难。组织东西要以它们之间的关系和使用顺序为依据,例如:
在实践中,通过减少为获得全貌而需要跳转的文件数量,可以显著减少脑力开销。
一个常见的例子是Spring JPA库,它使包变得混乱。可以把它们重新组织到同一个文件中:
上述内容的最终结果是代码行数(LOC)显著减少。这直接影响了交付速度和可维护性。
我们统计了Java项目中移植到Kotlin的文件数量和代码行数。这是一个典型的REST服务,包含数据模型、一些逻辑和缓存。在Kotlin版本中,LOC减少了大约50%。开发人员在跨文件浏览和编写样板代码上消耗的时间明显减少。
编写简洁的代码是一个宽泛的话题,这取决于语言、设计和技术的结合。然而,Kotlin提供了一个良好的工具集,为团队的成功奠定了基础。下面是一些例子。
类型推断最终会减少代码中的噪音。这有助于开发人员关注代码的目标。
类型推断可能会增加我们跟踪正在处理的对象的难度,这是一种常见的担忧。从实际经验来看,这种担忧只在少数情况下有必要,通常少于5%。在大多数情况下,类型是显而易见的。
下面的例子:
变成了:
在Kotlin中,也可以指定类型:
值得注意的是,Kotlin提供了一个全面的解决方案。例如,在Kotlin中,我们可以将函数类型定义为:
另一方面,Java 10通过检查右边表达式的类型推断类型。这引入了一些限制。如果我们尝试在Java中执行上述操作,我们会得到一个错误:
这是Kotlin中一个方便的特性,它允许我们为现有类型分配