本文档旨在为 Java 和 Kotlin 的公共 API 编写提供一套规则,以确保代码在被另一种语言调用时具有符合惯用法的体验。
Java(供 Kotlin 使用)
避免使用硬关键字
不要使用任何 Kotlin 的 硬关键字 作为方法或字段的名称。从 Kotlin 调用时,这些名称需要使用反引号进行转义。允许使用 软关键字、修饰符关键字 和 特殊标识符。
例如,在 Kotlin 中使用 Mockito 的 when 函数时需要使用反引号
val callable = Mockito.mock(Callable::class.java)
Mockito.`when`(callable.call()).thenReturn(/* … */)
避免 Any 扩展名称
除非绝对必要,否则应避免在方法名或字段名中使用 Any 的扩展函数 名称或 Any 的扩展属性 名称。虽然成员方法和字段的优先级总是高于 Any 的扩展函数或属性,但在阅读代码时可能很难分辨具体调用的是哪一个。
可空性注解
公共 API 中的每个非基本数据类型参数、返回值和字段类型都应具有可空性注解。未注解的类型会被解释为 “平台”类型,其可空性是不明确的。
默认情况下,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)
如果您希望方法作为属性公开,请不要使用非标准前缀,例如 has、set 或非 get 前缀的访问器。带有非标准前缀的方法仍然可以作为函数调用,根据方法的行为,这可能是可以接受的。
运算符重载
请注意那些允许特殊调用点语法(如 Kotlin 中的 运算符重载)的方法名。确保以这种方式命名的方法在使用缩短语法时逻辑通顺。
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 和 Java 中使用 Lambda 语法实现,这种方式以惯用的方式内联了实现。Kotlin 有多种定义此类接口的选项,每种选项略有不同。
推荐的定义
旨在供 Java 使用的 高阶函数 不应采用返回 Unit 的 函数类型,因为这会要求 Java 调用者返回 Unit.INSTANCE。与其在签名中内联函数类型,不如使用 函数式 (SAM) 接口。此外,在定义预期用作 Lambda 的接口时,考虑使用 函数式 (SAM) 接口,这允许在 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 语法是不合理的。Comparable 就是一个显著的例子,因为它旨在将 this 与 other 进行比较,而 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 端并没有这种强制执行。如果没有包装器或防御性拷贝,返回长期持有的集合引用可能会破坏不变量。
伴生对象函数
伴生对象 (companion object) 中的公共函数必须使用 @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 注解,才能被公开为静态字段。
如果没有该注解,这些属性只能作为静态 Companion 字段上名称奇怪的实例“getter”使用。使用 @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 时,请检查生成的每个方法以确保其合理。如果不是,请执行以下一种或多种重构,直到满意为止
- 更改参数顺序,使具有默认值的参数尽量靠后。
- 将默认值移至手动函数重载中。
错误:无 @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 使用)中的问题。具体支持的检查如下
- 未知可空性 (Unknown Nullness)
- 属性访问 (Property Access)
- 无硬 Kotlin 关键字 (No Hard Kotlin keywords)
- Lambda 参数置后 (Lambda Parameters Last)
Android Studio
要启用这些检查,请转到 File > Preferences > Editor > Inspections,并在 Kotlin Interoperability 下勾选您想要启用的规则

图 1. Android Studio 中的 Kotlin 互操作性设置。
勾选您想要启用的规则后,新的检查将在您运行代码检查时执行(Analyze > Inspect Code…)
命令行构建
要从命令行构建中启用这些检查,请在您的 build.gradle 文件中添加以下行
Groovy
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
有关 lintOptions 中支持的完整配置集,请参考 Android Gradle DSL 参考。
然后,在命令行中运行 ./gradlew lint。