Kotlin-Java 互操作指南

本文档是用 Java 和 Kotlin 编写公共 API 的一系列规则 目的是让用户在使用其他代码时感觉代码符合语言习惯 语言。

上次更新日期:2024 年 7 月 29 日

Java(供 Kotlin 使用)

不得使用硬关键字

不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。从 Kotlin。软关键字修饰符关键字和 允许使用特殊标识符

例如,从 Kotlin 使用时,Mockito 的 when 函数需要反引号:

val callable = Mockito.mock(Callable::class.java)
Mockito.`when`(callable.call()).thenReturn(/* … */)

避免使用 Any 的扩展函数或属性的名称

避免将 Any 扩展函数的名称用于 方法或 Any 的扩展属性的名称, 字段。成员方法和字段将始终 优先于 Any 的扩展函数或属性,因此可以 在读取代码时很难知道调用的是哪一个。

可为 null 性注释

公共 API 中的每个非基元参数类型、返回类型和字段类型都应 具有可为 null 性注解。未加注解的类型会被解释为 “平台”类型,这些类型是否可为 null 性不明确。

默认情况下,Kotlin 编译器标志接受 JSR 305 注解,但对它们进行标记 出现警告。您还可以设置一个标志,以使编译器将注解视为错误。

Lambda 参数位于最后

符合 SAM 转换条件的参数类型应位于最后。

例如,RxJava 2 的 Flowable.create() 方法签名定义为:

public static <T> Flowable<T> create(
    FlowableOnSubscribe<T> source,
    BackpressureStrategy mode) { /* … */ }

由于 FlowableOnSubscribe 符合 SAM 转换条件,因此 此方法在 Kotlin 中如下所示:

Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)

不过,如果方法签名中的参数颠倒顺序,则函数会调用 可以使用尾随 lambda 语法:

Flowable.create(BackpressureStrategy.LATEST) { /* … */ }

属性前缀

对于在 Kotlin 中要表示为属性的方法,需要严格的“bean”样式 前缀。

访问器方法需要 get 前缀;对于布尔值返回方法,则为 is 前缀。

public final class User {
  public String getName() { /* … */ }
  public boolean isActive() { /* … */ }
}
val name = user.name // Invokes user.getName()
val active = user.isActive // Invokes user.isActive()

关联的更改器方法需要 set 前缀。

public final class User {
  public String getName() { /* … */ }
  public void setName(String name) { /* … */ }
  public boolean isActive() { /* … */ }
  public void setActive(boolean active) { /* … */ }
}
user.name = "Bob" // Invokes user.setName(String)
user.isActive = true // Invokes user.setActive(boolean)

如果您希望方法作为属性公开,请不要使用非标准前缀,例如 hasset 或无 get 前缀的访问器。带有非标准前缀的方法 也可作为函数进行调用,具体取决于 方法的行为。

运算符过载

请注意允许特殊调用点语法(如 运算符过载)。请确保方法名称为 与缩短的语法一起使用有意义。

public final class IntBox {
  private final int value;
  public IntBox(int value) {
    this.value = value;
  }
  public IntBox plus(IntBox other) {
    return new IntBox(value + other.value);
  }
}
val one = IntBox(1)
val two = IntBox(2)
val three = one + two // Invokes one.plus(two)

Kotlin(供 Java 使用)

文件名

当文件包含顶级函数或属性时,请始终为其添加注解 使用 @file:JvmName("Foo") 提供一个好记的名称。

默认情况下,MyClass.kt 文件中的顶级成员最终将位于名为 MyClassKt,这种做法没有吸引力,并且会泄露作为实现的语言 。

建议您添加“@file:JvmMultifileClass”,以合并来自以下账号的顶级成员: 转换为单个类。

Lambda 参数

使用 Java 定义的单一方法接口 (SAM) 可以用两种 Kotlin 语言实现 以及使用 lambda 语法的 Java,后者以惯用方式内嵌实现 。Kotlin 提供了多种定义此类接口的选项,每个选项都对 差异。

首选定义

旨在从 Java 中使用的高阶函数 不应接受会返回 Unit 的函数类型 要求 Java 调用方返回 Unit.INSTANCE。无需内联函数 输入签名,请使用功能 (SAM) 接口。此外, 考虑使用功能 (SAM) 接口,而不是常规接口 在定义预期用作 lambda 的接口时,指定这些接口, 支持 Kotlin 中的惯用用法。

请参考以下 Kotlin 定义:

fun interface GreeterCallback {
  fun greetName(String name)
}

fun sayHi(greeter: GreeterCallback) = /* … */

从 Kotlin 调用时:

sayHi { println("Hello, $it!") }

从 Java 调用时:

sayHi(name -> System.out.println("Hello, " + name + "!"));

即使函数类型不会返回 Unit,仍建议您将其设为命名接口,以便调用方使用命名类来实现它,而非只使用 lambda(在 Kotlin 和 Java 中)。

class MyGreeterCallback : GreeterCallback {
  override fun greetName(name: String) {
    println("Hello, $name!");
  }
}

避免使用会返回 Unit 的函数类型

请参考以下 Kotlin 定义:

fun sayHi(greeter: (String) -> Unit) = /* … */

它要求 Java 调用方返回 Unit.INSTANCE

sayHi(name -> {
  System.out.println("Hello, " + name + "!");
  return Unit.INSTANCE;
});

如果实现应具有状态,请避免使用功能接口

当接口实现应具有状态时,使用 lambda 语法没有意义。类似就是一个突出的例子, 因为它用于将 thisother 进行比较,而 lambda 没有 this。非 为接口添加 fun 前缀会强制调用方使用 object : ... 语法,这使其具有状态,从而向调用方提供提示。

请参考以下 Kotlin 定义:

// No "fun" prefix.
interface Counter {
  fun increment()
}

这样就无法在 Kotlin 中使用 lambda 语法,而需以下较长的版本:

runCounter(object : Counter {
  private var increments = 0 // State

  override fun increment() {
    increments++
  }
})

避免使用 Nothing 类属

泛型参数为 Nothing 的类型会作为原始类型提供给 Java。原始 类型在 Java 中很少使用,应予以避免使用。

记录异常

会抛出受检异常的函数应使用 @Throws 记录这些异常。运行时异常应记录在 KDoc 中。

请注意函数委托给的 API,因为它们可能会抛出 Kotlin 本来会以静默方式允许传播的受检异常。

防御性复制

从公共 API 返回共享或无主的只读集合时,封装 或执行防御性复制尽管 Kotlin 其只读属性,则在 Java 环境中没有此类强制执行, 一面。如果没有封装容器或不执行防御性复制,就可能违反不变量 返回一个长期有效的集合引用。

伴生函数

伴生对象中的公共函数必须带有 @JvmStatic 注解 公开为静态方法

如果没有该注解,这些函数只能作为实例方法使用 。Companion

不正确:没有注解

class KotlinClass {
    companion object {
        fun doWork() {
            /* … */
        }
    }
}
public final class JavaClass {
    public static void main(String... args) {
        KotlinClass.Companion.doWork();
    }
}

正确:@JvmStatic 注释

class KotlinClass {
    companion object {
        @JvmStatic fun doWork() {
            /* … */
        }
    }
}
public final class JavaClass {
    public static void main(String... args) {
        KotlinClass.doWork();
    }
}

伴生常量

作为 companion object 中的有效常量的公共非 const 属性必须带有 @JvmField 注解,才能作为静态字段提供。

如果没有该注解,这些属性将只能作为奇怪的命名 实例“getters”针对静态 Companion 字段。改用 @JvmStatic@JvmField 移动了名字奇怪的“getter”类上的静态方法, 还是不正确。

不正确:没有注解

class KotlinClass {
    companion object {
        const val INTEGER_ONE = 1
        val BIG_INTEGER_ONE = BigInteger.ONE
    }
}
public final class JavaClass {
    public static void main(String... args) {
        System.out.println(KotlinClass.INTEGER_ONE);
        System.out.println(KotlinClass.Companion.getBIG_INTEGER_ONE());
    }
}

不正确:@JvmStatic 注释

class KotlinClass {
    companion object {
        const val INTEGER_ONE = 1
        @JvmStatic val BIG_INTEGER_ONE = BigInteger.ONE
    }
}
public final class JavaClass {
    public static void main(String... args) {
        System.out.println(KotlinClass.INTEGER_ONE);
        System.out.println(KotlinClass.getBIG_INTEGER_ONE());
    }
}

正确:@JvmField 注释

class KotlinClass {
    companion object {
        const val INTEGER_ONE = 1
        @JvmField val BIG_INTEGER_ONE = BigInteger.ONE
    }
}
public final class JavaClass {
    public static void main(String... args) {
        System.out.println(KotlinClass.INTEGER_ONE);
        System.out.println(KotlinClass.BIG_INTEGER_ONE);
    }
}

符合语言习惯的命名

Kotlin 的调用规范与 Java 不同,这可能会改变您为函数命名的方式。使用 @JvmName 设计符合语言习惯的名称 或匹配各自的标准库 命名。

扩展函数和扩展属性最常出现这种情况 因为接收器类型的位置不同。

sealed class Optional<T : Any>
data class Some<T : Any>(val value: T): Optional<T>()
object None : Optional<Nothing>()

@JvmName("ofNullable")
fun <T> T?.asOptional() = if (this == null) None else Some(this)
// FROM KOTLIN:
fun main(vararg args: String) {
    val nullableString: String? = "foo"
    val optionalString = nullableString.asOptional()
}
// FROM JAVA:
public static void main(String... args) {
    String nullableString = "Foo";
    Optional<String> optionalString =
          Optionals.ofNullable(nullableString);
}

默认值的函数过载

参数具有默认值的函数必须使用 @JvmOverloads。如果没有此注解,则无法使用任何默认值来调用函数。

使用 @JvmOverloads 时,检查生成的方法,确保它们 合理。否则,请执行以下一项或两项重构 直到满意为止:

  • 更改参数顺序,优先选择具有默认值的参数 end 的值。
  • 将默认值移至手动函数过载。

不正确:没有 @JvmOverloads

class Greeting {
    fun sayHello(prefix: String = "Mr.", name: String) {
        println("Hello, $prefix $name")
    }
}
public class JavaClass {
    public static void main(String... args) {
        Greeting greeting = new Greeting();
        greeting.sayHello("Mr.", "Bob");
    }
}

正确:@JvmOverloads 注释

class Greeting {
    @JvmOverloads
    fun sayHello(prefix: String = "Mr.", name: String) {
        println("Hello, $prefix $name")
    }
}
public class JavaClass {
    public static void main(String... args) {
        Greeting greeting = new Greeting();
        greeting.sayHello("Bob");
    }
}

Lint 检查

要求

  • Android Studio 版本:3.2 Canary 10 或更高版本
  • Android Gradle 插件版本:3.2 或更高版本

支持的检查

Android Lint 检查功能现在可以帮助您检测并标记 互操作性问题。仅 Java(对于 Kotlin)中的问题 消耗量)。具体来说,支持的检查包括:

  • 未知 Null 性
  • 属性访问
  • 不得使用 Kotlin 硬关键字
  • Lambda 参数位于最后

Android Studio

要启用这些检查,请依次点击 File >偏好设置 >编辑器 >检查和 在“Kotlin Interoperability”下选中您要启用的规则:

图 1. Android Studio 中的 Kotlin 互操作性设置。

选中要启用的规则后,新的检查将 在运行代码检查 (Analyze > Inspect Code...) 时运行

命令行 build

如需从命令行 build 启用这些检查,请将以下代码行添加到 您的 build.gradle 文件:

Groovy

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

如需了解 lintOptions 内支持的全部配置,请参阅 Android Gradle DSL 参考文档

然后,从命令行运行 ./gradlew lint