SpringBoot项目混合开发:Java与Kotlin互操作实践与Maven配置详解

发布时间:2026/8/3 12:21:56
SpringBoot项目混合开发:Java与Kotlin互操作实践与Maven配置详解 1. 项目概述为什么要在SpringBoot中引入Kotlin如果你是一个常年使用Java开发SpringBoot项目的工程师最近可能频繁听到团队里有人讨论Kotlin。不是因为它是什么全新的东西而是大家发现在保持原有Java代码库稳定的前提下逐步引入Kotlin能带来一种“平滑升级”的开发体验。我最近就在一个基于Maven的老牌SpringBoot项目里做了这件事把Kotlin接了进来形成了Java和Kotlin文件共存的混合开发模式。这绝不是为了追赶时髦而是实打实地为了解决一些Java开发中略显繁琐的问题同时又不至于让团队陷入重写所有代码的焦虑。简单来说这个“混合开发”项目核心目标就是让你现有的SpringBootMaven项目能够同时编译和运行.java和.kt文件。Java的稳健和庞大生态我们继续用而Kotlin的简洁语法、空安全特性、扩展函数等“糖”我们可以按需、渐进地在新的业务模块或者工具类中尝试。想象一下你写新的Service层时用Kotlin几行代码就搞定了原本需要一堆样板代码的逻辑而且还能和原有的Java Controller、Entity无缝互相调用这种感觉就像给你的老伙计Java装上了一套更顺手的工具而不是要换掉他。那么谁适合考虑做这件事呢首先是正在维护大型、历史悠久的Java SpringBoot项目的团队推倒重来成本太高混合开发是稳妥的演进路径。其次是对开发效率有更高追求的个人或团队想尝试现代语言特性但又被Java版本或历史包袱限制。最后当然也包括那些对Kotlin感兴趣想在一个真实、有上下游依赖的项目里实践而不是仅仅停留在“Hello World”阶段的开发者。接下来我会详细拆解从零开始接入的每一步包括我踩过的坑和最终验证可行的配置方案。2. 混合开发环境搭建与Maven核心配置在开始写任何Kotlin代码之前环境的搭建是重中之重。这里最大的依赖就是Maven我们需要通过它来统一管理Java和Kotlin的编译插件、依赖库。整个过程的核心思想是让Maven的编译生命周期能够识别并处理Kotlin源代码。2.1 项目结构与依赖准备首先确保你的项目是一个标准的Maven管理的SpringBoot项目。项目结构大致如下你需要关注的是src/main下的源码目录。混合开发意味着java和kotlin两个源码目录需要共存。一种常见的、也是Maven社区插件推荐的做法是将Kotlin源代码放在src/main/kotlin目录下Java代码依然保持在src/main/java。Maven插件会智能地识别并编译这两个目录。your-springboot-project ├── pom.xml ├── src │ ├── main │ │ ├── java # 原有的Java源代码 │ │ │ └── com │ │ │ └── yourcompany │ │ │ └── Application.java │ │ ├── kotlin # 新增的Kotlin源代码目录 │ │ │ └── com │ │ │ └── yourcompany │ │ │ └── service │ │ │ └── DemoService.kt │ │ └── resources │ └── test └── target接下来打开项目的pom.xml文件。我们需要引入Kotlin的标准库、运行时以及最重要的——Maven编译插件。2.2 POM.xml 关键配置详解配置是混合开发成功的关键下面我给出一个经过实测的、完整的配置片段并逐一解释每个部分的作用。1. 属性定义在properties标签内定义Kotlin的版本号。统一管理版本号是个好习惯。properties kotlin.version1.9.24/kotlin.version !-- 建议使用较新稳定版 -- java.version11/java.version !-- 根据你的项目情况设定 -- /properties2. 依赖配置在dependencies部分添加Kotlin标准库和运行时库。注意kotlin-stdlib-jdk8或kotlin-stdlib-jdk11是针对不同JDK版本的通常选jdk8兼容性最好。SpringBoot的依赖保持不变。dependencies !-- SpringBoot Starter 依赖根据你的需要添加 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Kotlin 标准库 (JDK8) -- dependency groupIdorg.jetbrains.kotlin/groupId artifactIdkotlin-stdlib-jdk8/artifactId version${kotlin.version}/version /dependency !-- Kotlin 反射库Spring等框架可能需要 -- dependency groupIdorg.jetbrains.kotlin/groupId artifactIdkotlin-reflect/artifactId version${kotlin.version}/version /dependency !-- 可选Kotlin协程核心库如需使用协程特性 -- dependency groupIdorg.jetbrains.kotlinx/groupId artifactIdkotlinx-coroutines-core/artifactId version1.8.0/version /dependency /dependencies3. 构建插件配置核心这是最核心的一步在build的plugins里配置Kotlin Maven插件。它的作用是在Maven的compile阶段编译Kotlin代码在test-compile阶段编译Kotlin测试代码。build plugins !-- 1. Kotlin Maven 插件 -- plugin groupIdorg.jetbrains.kotlin/groupId artifactIdkotlin-maven-plugin/artifactId version${kotlin.version}/version executions execution idcompile/id phasecompile/phase goals goalcompile/goal /goals configuration sourceDirs sourceDir${project.basedir}/src/main/kotlin/sourceDir sourceDir${project.basedir}/src/main/java/sourceDir /sourceDirs /configuration /execution execution idtest-compile/id phasetest-compile/phase goals goaltest-compile/goal /goals configuration sourceDirs sourceDir${project.basedir}/src/test/kotlin/sourceDir sourceDir${project.basedir}/src/test/java/sourceDir /sourceDirs /configuration /execution /executions /plugin !-- 2. 确保Maven编译插件在Kotlin之后执行处理Java代码 -- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version executions execution iddefault-compile/id phasenone/phase !-- 禁用默认的Java编译 -- /execution execution iddefault-testCompile/id phasenone/phase !-- 禁用默认的Java测试编译 -- /execution execution idjava-compile/id phasecompile/phase goals goalcompile/goal /goals /execution execution idjava-test-compile/id phasetest-compile/phase goals goaltestCompile/goal /goals /execution /executions /plugin !-- 3. SpringBoot Maven 插件 (用于打包可执行JAR) -- plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build注意上面配置中sourceDirs的配置非常关键它显式地告诉Kotlin插件需要编译的源代码目录包括了Java目录。这是因为Kotlin代码可能会调用Java代码需要一起参与编译分析。而我们将maven-compiler-plugin的默认执行阶段设为none再重新绑定到compile和test-compile阶段是为了确保Java编译在Kotlin编译之后执行实际上由于phase相同执行顺序由插件声明顺序决定但这样配置更清晰可靠。2.3 IDE支持与配置我强烈推荐使用IntelliJ IDEA进行混合开发。它对Kotlin的支持是原生的体验最好。打开项目直接用IDEA打开你的Maven项目。目录标记IDEA通常会自动识别src/main/kotlin为源代码根目录。如果没有可以右键该目录 -Mark Directory as-Sources Root。语言级别确保项目的SDK和语言级别设置正确。在File-Project Structure-Project中设置Project SDK为你的JDK如11Project language level可以设置为与SDK匹配或更高。启用注解处理如果你的项目使用了Lombok、MapStruct等注解处理器需要在Settings-Build, Execution, Deployment-Compiler-Annotation Processors中勾选Enable annotation processing。这里有一个巨坑Kotlin和Java的注解处理需要分别配置。对于Kotlin你还需要在Kotlin Compiler设置中确保Annotation Processing也是启用的。配置完成后你应该可以同时在项目中创建.java和.kt文件并且IDEA能提供正确的语法高亮、代码补全和跳转。3. Java与Kotlin互操作实践详解环境配好了接下来就是最激动人心的部分让Java和Kotlin代码互相调用。得益于JetBrains的设计这种互操作在大部分情况下是平滑无感的但了解其中的一些细节和“边界情况”能让你避免很多运行时错误。3.1 从Java调用Kotlin代码Kotlin编译成JVM字节码后会遵循Java的约定。但Kotlin有一些自己的特性在Java中调用时需要稍加注意。1. 顶级函数与扩展函数在Kotlin中你可以直接在文件顶层定义函数顶级函数。在Java中这些函数会被编译成一个以该Kotlin文件名Kt后缀的静态工具类。// File: StringUtils.kt package com.yourcompany.util fun isNullOrBlank(value: String?): Boolean { // 顶级函数 return value.isNullOrBlank() } fun String.addExclamation(): String { // 扩展函数 return this ! }在Java中这样调用import com.yourcompany.util.StringUtilsKt; // 注意类名是 文件名Kt public class JavaCaller { public void demo() { Boolean result StringUtilsKt.isNullOrBlank(null); // 调用顶级函数 String str StringUtilsKt.addExclamation(Hello); // 调用扩展函数第一个参数是接收者对象 } }实操心得如果你觉得生成的XXXKt类名不好看可以在Kotlin文件顶部使用file:JvmName(“DesiredName”)注解来指定生成的Java类名。例如file:JvmName(“StringUtils”)这样在Java中就可以用StringUtils.isNullOrBlank(...)调用了。2. 空安全与平台类型这是互操作中最需要小心的地方。Kotlin的空安全类型String非空String?可空在Java端会失去约束。一个Kotlin中声明为非空的参数在Java中传入null会在运行时抛出IllegalArgumentException。// Kotlin 类 class KotlinService { fun process(name: String) { // 非空参数 println(name.length) } }// Java 调用方 KotlinService service new KotlinService(); service.process(null); // 编译通过但运行时会抛出异常IllegalArgumentException: Parameter specified as non-null is null给你的Java代码上保险在Java端调用Kotlin函数时如果参数在Kotlin中是非空的请务必确保不传null。IDEA通常会在Java代码中给出警告提示。3. 数据类、伴生对象与默认参数数据类自动生成的componentN()、copy()、toString()等方法在Java中都可以正常使用。伴生对象在Java中通过类名.Companion来访问。例如KotlinClass.Companion.staticMethod()。同样可以用JvmStatic注解让方法暴露为真正的Java静态方法。默认参数Kotlin函数的默认参数在Java中是不可见的。Java调用时必须传入所有参数。如果需要Java也能使用默认值可以用JvmOverloads注解该函数编译器会为其生成多个重载版本。3.2 从Kotlin调用Java代码这部分通常更简单因为Kotlin被设计成对Java有极佳的互操作性。1. Getter/Setter与属性Kotlin会将Java类的符合Bean规范的getter/setter方法视为属性。例如一个Java类JavaPojo有getName()和setName()方法在Kotlin中你可以直接使用obj.name来读写。// Java public class JavaPojo { private String data; public String getData() { return data; } public void setData(String data) { this.data data; } }// Kotlin val pojo JavaPojo() pojo.data Hello // 调用 setData println(pojo.data) // 调用 getData2. 空处理与平台类型当Kotlin调用一个返回类型为T的Java方法时Kotlin无法从Java的字节码中得知这个T是否可空。这种类型在Kotlin中被称为平台类型表示为T!。你可以选择将其视为可空T?或非空T但需要自己承担风险。val list: ListString javaMethodReturnsList() // 如果Java方法返回null这里赋值时会抛出NPE val list2: ListString? javaMethodReturnsList() // 更安全的做法显式声明为可空注意事项对于关键的、可能返回null的Java API调用在Kotlin侧建议立即使用安全调用操作符?.或Elvis操作符?:进行处理或者使用!!在确信非空时断言。良好的习惯是在团队内对常用的Java库方法进行可空性标注使用Nullable/NotNull注解这样Kotlin编译器就能识别。3. 函数式接口与SAM转换如果Java接口只有一个抽象方法即函数式接口如Runnable,CallableKotlin允许使用SAMSingle Abstract Method转换用lambda表达式来简化实现。// Java 接口 public interface OnClickListener { void onClick(View v); }// Kotlin 调用 javaObject.setOnClickListener { view - // 直接使用lambda println(Clicked) } // 等同于 javaObject.setOnClickListener(OnClickListener { view - println(Clicked) })3.3 Spring框架中的混合使用在SpringBoot项目中混合开发最常见的场景就是用Java写Controller、Entity可能因为JPA注解或历史原因用Kotlin写Service、Repository或工具类。1. 依赖注入Spring的Autowired、Resource或者构造器注入在混合环境下完全透明。一个Kotlin的Service类可以被Java的RestController注入反之亦然。Service class KotlinUserService(val userRepository: UserRepository) { // 构造器注入 fun findUser(id: Long): UserDto ... }RestController public class JavaUserController { Autowired // 字段注入 private KotlinUserService userService; GetMapping(/user/{id}) public UserDto getUser(PathVariable Long id) { return userService.findUser(id); // 无缝调用 } }2. 注解使用大部分Spring注解在Kotlin中用法与Java一致。但需要注意几点Configuration和Bean定义配置类完全没问题。Transactional在Kotlin类上使用事务注解时确保相关方法是open的非final因为Spring AOP默认通过创建子类代理来实现需要方法可被重写。你可以给类添加open关键字或者使用spring-boot-starter-aop并配置使用基于接口的代理proxyTargetClassfalse或更现代的基于CGLIB的代理但Kotlin中的类默认是final的这仍是个问题。最简单的办法是给Kotlin服务类加上open修饰符或者使用all-open编译器插件后面会讲。Lombok在Kotlin中无法使用Lombok。如果你的Entity是Java的继续用Lombok没问题。如果是Kotlin的请使用Kotlin的数据类data class它自动提供了equals(),hashCode(),toString(),copy()和组件函数。3. JPA Entity用Kotlin定义JPA Entity是完全可行的但有一些细微差别。Entity data class User( Id GeneratedValue(strategy GenerationType.IDENTITY) val id: Long? null, // 可空因为新增时id为空 val username: String, val email: String, // 注意数据类的属性默认在构造函数中JPA需要无参构造器 ) { // JPA规范要求一个无参构造器数据类默认没有。可以这样提供 protected constructor() : this(null, , ) }重要提示如上例所示Kotlin数据类的主构造函数包含了所有属性。JPA要求实体类有一个无参构造函数。我们可以通过提供一个受保护的、调用主构造函数的次构造函数来满足要求。同时将id设为可空Long?是一个常见的做法因为新增实体时它还没有值。4. 编译、打包与常见问题排查当代码写完后我们需要通过Maven命令进行编译、测试和打包。混合项目在此过程中可能会遇到一些特有的问题。4.1 Maven生命周期命令执行在项目根目录下执行Maven命令其生命周期会依次触发我们配置的插件。清理并编译mvn clean compile这个命令会先清理target目录然后执行compile阶段。Kotlin插件会先编译src/main/kotlin和src/main/java目录下的所有源代码。随后maven-compiler-plugin会编译Java源代码虽然大部分已被Kotlin插件处理过但这样配置确保了兼容性。观察控制台输出你应该能看到类似[INFO] kotlin-maven-plugin:compile和[INFO] maven-compiler-plugin:compile的执行日志。运行测试mvn test这会执行test-compile和test阶段。Kotlin插件会编译src/test/kotlin和src/test/java下的测试代码然后Surefire插件会运行所有测试。你可以混合使用JUnit、TestNG以及Kotlin的测试框架如KotlinTest或Spek。打包可执行JARmvn clean package这是最常用的命令。它会执行完整的生命周期compile, test, package。spring-boot-maven-plugin会收集所有依赖并打包成一个包含嵌入式Web服务器如Tomcat的可执行Fat JAR。关键点打包后的JAR中Kotlin的运行时库kotlin-stdlib-*.jar会被自动包含进去无需额外担心。运行应用打包后使用java -jar target/your-app-0.0.1-SNAPSHOT.jar即可启动SpringBoot应用。你也可以在开发时直接用mvn spring-boot:run来运行这个命令同样能正确处理混合代码。4.2 高频问题与解决方案实录在实际操作中我遇到了不少问题下面这个表格整理了几个最典型的问题现象可能原因解决方案编译错误unresolved reference(找不到Java类)1. Kotlin编译先于Java编译。2.sourceDirs配置遗漏了Java目录。3. 依赖的Java模块未正确引入。1. 检查并确保pom.xml中kotlin-maven-plugin的sourceDirs包含了Java源码目录如我们之前的配置。2. 确认Maven依赖已正确添加尝试mvn clean compile。运行时错误IllegalArgumentException: Parameter specified as non-null is nullJava代码向Kotlin的非空参数传递了null。1. 检查Java调用方确保不传递null。2. 在Kotlin函数参数上使用可空类型(?)并在函数体内处理空值。3. 在Java调用处使用NotNull注解如果使用JetBrains或JSR-305注解以获取IDE警告。Spring Bean注入失败或事务不生效Kotlin类或方法默认是final的Spring的CGLIB代理无法继承。1. 为需要被代理的类和方法添加open关键字。2.推荐使用kotlin-spring编译器插件它会自动为带有Spring注解的类和方法打开open。在pom.xml的kotlin-maven-plugin配置中添加configurationcompilerPluginspluginspring/plugin/compilerPlugins/configuration并添加依赖kotlin-maven-plugin的spring插件。Lombok在Kotlin中无法使用Lombok的注解处理器不处理Kotlin源代码。1. 对于Entity等数据模型如果要在Kotlin中使用放弃Lombok改用Kotlin的data class。2. 如果模型是Java的Lombok正常工作无需改动。3. 确保IDEA中为Kotlin也启用了注解处理Settings Build Compiler Kotlin Compiler Annotation Processing。mvn spring-boot:run可以但打包后运行报错打包时可能遗漏了资源文件或Kotlin相关依赖未正确打包。1. 检查spring-boot-maven-plugin配置是否正确。2. 使用mvn dependency:tree检查运行时依赖是否包含kotlin-stdlib和kotlin-reflect。3. 解压生成的JAR包(jar tf target/*.jar)查看BOOT-INF/classes下是否有你的.class文件BOOT-INF/lib下是否有Kotlin的jar。IDEA提示错误但Maven命令编译通过IDEA的构建系统和Maven不同步或者缓存问题。1. 尝试File-Invalidate Caches / Restart...。2. 在Maven工具窗口点击Reimport All Maven Projects。3. 检查IDEA的Project Structure中模块的依赖和语言级别是否与pom.xml一致。4.3 编译器插件与进阶配置为了获得更好的Spring框架兼容性和开发体验我强烈建议使用Kotlin编译器插件。1.kotlin-spring插件如前所述这个插件会自动为标注了Spring注解的类、方法、属性打开open省去手动写open的麻烦。 在pom.xml的kotlin-maven-plugin中添加配置plugin groupIdorg.jetbrains.kotlin/groupId artifactIdkotlin-maven-plugin/artifactId version${kotlin.version}/version configuration compilerPlugins pluginspring/plugin !-- 还可以添加其他插件如 all-open, no-arg -- /compilerPlugins /configuration dependencies dependency groupIdorg.jetbrains.kotlin/groupId artifactIdkotlin-maven-allopen/artifactId version${kotlin.version}/version /dependency /dependencies ... !-- 之前的executions配置 -- /plugin2.kotlin-noarg插件这个插件会为标注了特定注解的类生成一个无参构造函数。这对JPA Entity特别有用可以让我们不用手动写那个受保护的无参构造器。 配置方式与all-open类似需要添加no-arg插件依赖和配置并指定触发注解如javax.persistence.Entity。compilerPlugins pluginno-arg/plugin /compilerPlugins ... dependency groupIdorg.jetbrains.kotlin/groupId artifactIdkotlin-maven-noarg/artifactId version${kotlin.version}/version /dependency在configuration中还可以细化configuration compilerPlugins pluginno-arg/plugin /compilerPlugins pluginOptions optionno-arg:annotationjavax.persistence.Entity/option optionno-arg:annotationjavax.persistence.Embeddable/option /pluginOptions /configuration3. JVM目标字节码版本确保Kotlin编译出的字节码版本与你项目使用的JDK版本匹配。在插件全局配置或属性中设置properties kotlin.version1.9.24/kotlin.version java.version11/java.version kotlin.compiler.jvmTarget11/kotlin.compiler.jvmTarget !-- 与java.version一致 -- /properties或者在插件配置中configuration jvmTarget11/jvmTarget /configuration5. 混合开发策略与团队协作建议将Kotlin引入一个成熟的Java项目不仅仅是技术配置更涉及工作流程和团队习惯的调整。根据我的经验采取渐进式、低风险的策略是成功的关键。5.1 渐进式引入策略不要试图一次性将整个项目迁移到Kotlin。这会让团队感到压力并引入大量不可控风险。我推荐以下步骤第零步技术预研与试点。由一个或几个对此感兴趣的开发者在一个独立的、不关键的特性分支上完成本文所述的环境搭建和基础互操作验证。写几个简单的工具类或服务类进行测试确保编译、运行、测试、打包整个流程畅通。第一步工具类与工具函数。这是引入Kotlin最安全的地方。将项目中那些无状态、纯功能的工具类如日期处理、字符串操作、加密解密等用Kotlin重写。因为这些类通常不涉及复杂的框架交互和状态管理用Kotlin的扩展函数、顶级函数写起来更简洁并且能立刻被所有Java代码调用让团队直观感受到Kotlin的好处。第二步新的业务模块或服务。当有新功能需要开发时可以尝试用Kotlin来编写整个Service层或Repository层。这样可以在一个相对独立的环境里实践Kotlin的特性如数据类、空安全、DSL等而不会影响已有的核心业务逻辑。同时这个新模块必须与原有的Java模块如Controller、Entity能正确交互。第三步逐步重构非核心旧代码。在团队对Kotlin有一定熟悉度后可以开始有计划地重构一些逻辑复杂、但非核心的旧Java代码。每次重构的范围要小并且要有完整的单元测试覆盖确保重构不会破坏现有功能。第四步核心业务逻辑谨慎。对于项目的核心、稳定且复杂的业务逻辑除非有非常明确的收益如利用协程简化高并发否则不建议轻易用Kotlin重写。维护成本和风险可能高于收益。5.2 团队协作与代码规范当团队中部分成员开始使用Kotlin时建立一些基本的协作规范非常重要。统一代码风格在项目根目录添加Kotlin官方的代码风格配置文件ktlint或detekt并集成到CI/CD流程中确保提交的Kotlin代码风格一致。IDEA也内置了Kotlin代码格式化工具可以配置团队共享的代码样式方案。互操作约定空安全边界明确团队约定在Java和Kotlin的边界处谁对空值负责。例如可以约定“所有从Java侧调用Kotlin公开API时调用方必须保证不传递null给非空参数”。或者更保守一点“所有对外暴露的Kotlin函数如果参数可能来自Java一律使用可空类型并在内部处理”。API设计设计供Java调用的Kotlin API时善用JvmStatic、JvmOverloads、JvmName等注解让Java侧的调用更符合习惯。异常处理Kotlin没有受检异常checked exception。当Kotlin函数调用可能抛出受检异常的Java方法时需要在Kotlin侧用Throws注解声明以便Java调用者能正确处理。知识分享与培训定期组织内部分享介绍Kotlin的特性、与Java互操作的技巧、以及在实际项目中遇到的坑和解决方案。鼓励“结对编程”让熟悉Kotlin的成员和Java成员一起工作加速知识传递。测试策略混合项目要特别重视测试。单元测试应该同时覆盖Java和Kotlin类并且要有意识地测试互操作场景如Java调用Kotlin函数传null、Kotlin调用返回平台类型的Java方法等。集成测试和API测试则能确保整个应用在混合环境下的行为符合预期。5.3 性能与可维护性考量性能Kotlin编译后的字节码与Java在性能上几乎没有差异。高阶函数、Lambda表达式等特性在运行时可能会生成一些额外的匿名类但在绝大多数业务场景下这点开销可以忽略不计。协程Coroutines在异步编程上相比回调或CompletableFuture能提供更高效的线程利用和更清晰的代码结构在处理高并发I/O时优势明显。可维护性从长期看Kotlin的简洁语法和空安全特性有助于减少代码量降低NullPointerException的发生率从而提高代码的可读性和可维护性。但是混合代码库本身会带来一定的认知负担新成员需要同时理解两种语言。因此清晰的模块边界例如某个子模块完全用Kotlin和良好的文档注释显得尤为重要。构建时间引入Kotlin编译器可能会略微增加项目的构建时间因为需要多处理一种语言。可以通过配置增量编译、使用构建缓存Gradle在这方面支持更好来缓解。对于Maven项目确保开发环境有足够的内存分配给Maven进程。我个人在实际操作中的体会是混合开发最大的挑战不在于技术而在于人和流程。技术问题都有明确的解决方案但让一个习惯了Java的团队接受并有效使用另一种语言需要时间、耐心和成功的试点案例。一旦团队跨过了最初的学习曲线并切实感受到了Kotlin在开发效率、代码安全性上带来的提升这种混合模式就会成为一个强有力的工具让项目在保持稳定的同时也能拥抱现代语言的优势。最后一个小技巧是在IDEA中你可以使用CtrlAltShiftK或通过菜单Code-Convert Java File to Kotlin File快速将单个Java文件转换为Kotlin文件这在做小范围重构时非常方便但转换后一定要仔细检查特别是空安全相关的逻辑。