为何应将微服务从Java迁移到Kotlin:经验与见解
了解为何向Kotlin迁移如此成功,以及为何开发者们即便此前仅接触过其他JVM语言,仍热衷于转向这门语言。
我就职于东欧最大的一家私人银行,负责开发一款移动应用的后端。我们的集群由400多个微服务组成,个别服务的峰值负载能达到五位数。最初向微服务架构转型时,我们所有的代码都是用Java编写的。但随着时间推移,我们开始积极将微服务迁移到Kotlin。如今,所有新的微服务都只用Kotlin创建,Java代码的占比已降至20%以下。
在本文中,我将解释为何向Kotlin的迁移如此成功,以及为何开发者们即便此前仅接触过其他JVM语言,仍热衷于转向这门语言。
Kotlin与Java的对比
Kotlin的主要优势之一是与Java代码完全兼容。Kotlin能与Java类和方法无缝交互,反之亦然,这使得向新语言的过渡十分顺畅,无需重写现有代码。Kotlin使用JVM字节码,确保了与我们项目中大量使用的所有Java库和框架的兼容性。
微服务的模块化结构非常适合逐步引入Kotlin。我们先使用Kotlin开发新组件,然后慢慢迁移旧组件。重要的是,我们可以继续使用Java代码,而无需担心冲突或故障。这种方法让我们规避了重大风险,确保了整个集群的稳定运行。
Kotlin提供了一系列现代化特性,简化了开发流程并提高了开发效率。以下是几个例子:
- 协程:便于高效编写异步代码,避免了Java多线程的复杂性。
- 扩展:允许在不修改现有类的情况下为其添加新函数。
- 可空类型 :解决了
NullPointerException
问题,这是Java开发者常遇到的难题 。
转向Kotlin既流畅又自然,因为它的语法与Java极为相似。然而,与Java不同的是,Kotlin省去了大量样板代码,使代码更加简洁易读。例如,在Kotlin中使用data class
,只需一行代码就能创建POJO类。在语言的其他方面,如集合操作、异步编程和可空类型处理,也有类似的改进。
代码示例:Java与Kotlin
为了说明这一点,以下是一些对比:
POJO类与数据类
Java
java
public class Person {
private String name;
private int age;
/*
此外,你还需要为每个字段添加构造函数、
获取器和设置器,
重写toString,
equals,
hashCode
*/
}
在Java中,创建一个简单的POJO(普通Java对象)需要显式定义字段、构造函数、获取器、设置器,并且通常要重写toString()
、equals()
和hashCode()
方法。这导致产生大量样板代码。
Kotlin
kotlin
data class Person(val name: String, val age: Int)
Kotlin的data class
会自动生成构造函数、获取器(对于var
属性还有设置器)、toString()
、equals()
、hashCode()
和copy()
方法。这一行代码相当于Java中的几十行代码。
可空类型
Java
java
String name = null;
if (name != null) {
System.out.println(name.length());
} else {
System.out.println("Name is null");
}
在Java中,访问对象前需要显式检查是否为null
,以避免NullPointerException
。这往往会导致冗长的空值检查代码。
Kotlin
kotlin
val name: String? = null
println(name?.length ?: "Name is null")
Kotlin使用安全调用操作符?.
和Elvis操作符?:
简洁地处理可空类型。这行代码在name
不为null
时安全地访问其length
属性,否则打印"Name is null"
。在Kotlin中,安全调用操作符?.
和用于默认值的?:
操作符简化了空值处理。
嵌套对象检查
Java
java
if (person != null && person.getAddress() != null && person.getAddress().getCity() != null) {
System.out.println(person.getAddress().getCity());
}
在Java中,检查嵌套对象是否为null
需要进行多次检查,这会导致嵌套很深的if
语句或冗长的布尔表达式。
Kotlin
kotlin
person?.address?.city?.let {
println(it)
}
Kotlin的安全调用操作符?.
允许链式调用可空对象。let
函数仅在链式调用中所有前置调用都不为null
时才执行代码块。
异步调用
Java
java
public Mono<String> fetchData() {
Mono<String> first = fetchFirst();
Mono<String> second = fetchSecond();
return Mono.zip(first, second)
.map(tuple -> tuple.getT1() + " " + tuple.getT2());
}
这段Java代码使用Project Reactor的Mono
进行异步编程。它异步获取两段数据并将其组合。
Kotlin
kotlin
suspend fun fetchData() {
val first = async { fetchFirst() }
val second = async { fetchSecond() }
println("${first.await()} ${second.await()}")
}
Kotlin的协程使异步代码在外观和行为上都类似同步代码。async
函数启动协程来获取数据,await()
则暂停执行,直到结果可用。
集合处理
Java
java
List<String> names = Arrays.asList("John", "Jane", "Doe");
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("J"))
.map(String::toUpperCase)
.collect(Collectors.toList());
Java使用流和Lambda表达式处理集合。这段代码过滤出以"J"
开头的名字,并将其转换为大写。
Kotlin
kotlin
val names = listOf("John", "Jane", "Doe")
val filteredNames = names.filter { it.startsWith("J") }
.map { it.uppercase() }
Kotlin为集合处理提供了简洁的函数。这段代码与Java版本功能相同,但语法更易读。Kotlin在处理集合时提供了更简洁、更具表现力的语法。
Java中的if-else与Kotlin中的when
Java
java
int number = 3;
String result;
if (number == 1) {
result = "One";
} else if (number == 2) {
result = "Two";
} else {
result = "Other";
}
Java使用传统的if-else
语句进行条件逻辑判断。当条件较多时,代码会变得冗长。
Kotlin
kotlin
val number = 3
val result = when (number) {
1 -> "One"
2 -> "Two"
else -> "Other"
}
Kotlin的when
表达式为switch
语句和复杂的if-else
链提供了更简洁、更强大的替代方案。
类扩展
Java
java
public class StringUtils {
public static String reverse(String s) {
return new StringBuilder(s).reverse().toString();
}
}
在Java中,为现有类添加功能通常需要创建包含静态方法的工具类。
Kotlin
kotlin
fun String.reverse(): String = this.reversed()
Kotlin的扩展函数允许在不修改现有类源代码的情况下为其添加新方法。这个函数用于反转字符串,并且可以直接在字符串对象上调用。
kotlin
val reversed = "Hello".reverse() // "olleH"
扩展函数可以像字符串类的方法一样被调用,使代码更直观、更具面向对象特性。
这些示例清晰地展示了,与Java相比,Kotlin如何通过简洁的语法、内置特性以及强大的异步编程和集合处理工具,简化并增强开发过程。
结论
说到在同一项目中Java和Kotlin的兼容性,我想强调的是,在项目中同时使用这两种语言编写微服务,无需创建单独的库和启动器。唯一的限制是Java版本,其不能高于服务中使用的版本。Spring也完全支持这两种语言,使用上的差异仅在于需要通过Maven或Gradle构建系统引入的部分依赖项。
在我们的项目中同时使用Java和Kotlin数年后,我可以自信地说,开发者们不想再回到只用Java的时代。他们更喜欢Kotlin,因为它更简洁、更具表现力,为编写高效代码提供了更多可能。Kotlin代码更易于阅读和理解,尤其是在定期审查同事的拉取请求时。对于有其他JVM语言经验的程序员来说,转向Kotlin的速度非常快。
当然,Java也在不断发展。在新版本中,出现了像记录(records)这样的特性,它类似于Kotlin中的数据类。虚拟线程(Virtual threads)也在开发中,其可能会取代协程。此外,对可空对象的支持也在不断改进。
尽管如此,我们的大多数开发者还是更喜欢使用Kotlin。他们欣赏Kotlin的简洁性、表现力以及能显著提高生产力和代码质量的现代化特性。对我们来说,从Java过渡到Kotlin既简单又自然,这使我们能够维护现有系统,同时逐步引入新语言。