Kotlin-Java 互通性指南

本文件是關於以 Java 和 Kotlin 編寫公用 API 的一組規則 這個元素結合了其他元素,讓使用者覺得程式碼在其他地方使用這些程式碼時,會感到符合慣用語的意圖 語言。

上次更新時間:2024 年 7 月 29 日

Java (搭配 Kotlin)

沒有硬關鍵字

請勿使用 Kotlin 的任何硬關鍵字做為方法名稱 或欄位這些物件在呼叫 Kotlin。軟性關鍵字修飾符關鍵字和 可以使用特殊 ID

例如,透過 Kotlin 使用 Mockito 的 when 函式時,需要倒引號:

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

避免使用 Any 擴充功能名稱

避免將 Any 上的擴充功能函式名稱用於 方法,或是 Any 上的擴充功能屬性名稱。 ] 欄位。成員方法和欄位一律會 優先順序高於 Any 的擴充功能函式或屬性,可 難以讀取程式碼,以判斷系統呼叫的是哪一個程式碼。

是否可為空值註解

公用 API 中的每個非原始參數、回傳和欄位類型都應 您就能使用是否可為空值註解未加註的類型會解讀為 "月台"Type,其中含有不明確的是否可為空值屬性。

根據預設,Kotlin 編譯器旗標會採用 JSR 305 註解,但會加以標記 ,但出現警告。您也可以設定旗標,讓編譯器將註解視為錯誤。

上次 Lambda 參數

符合 SAM 轉換資格的參數類型必須是最後一個類型。

例如,RxJava 2 的 Flowable.create() 方法簽章定義為:

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

由於 FlowableOnSubscribe 符合 SAM 轉換的資格, 如下所示:

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 引數

兩種 Kotlin 都能實作以 Java 定義的單一方法介面 (SAM) 和 Java 使用 lambda 語法,在慣用上將實作項目內嵌 。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 就沒有任何意義Comparable 就是明顯的例子 因為是用來比較 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,因為這些 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();
    }
}

夥伴常數

公開的非 const 屬性如果是 companion object 中的有效常數,則必須使用 @JvmField 加註,才能以靜態欄位的形式公開。

如果沒有註解,這些屬性僅會提供一個奇怪的名稱 執行個體「getter」靜態 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 時,請檢查產生的方法,確認每個方法皆符合 道理很合理如果無法解決,請執行下列一項或兩項重構作業 直到滿意:

  • 變更參數順序,先用預設向 結尾。
  • 將預設移入手動函式超載。

錯誤:無 @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 初期測試版本 10 或以上
  • Android Gradle 外掛程式版本:3.2 或以上

支援的檢查

現在,Android Lint 檢查功能可協助您偵測並標記 以及前述的互通性問題僅限 Java 中的問題 (適用於 Kotlin 消費)。具體來說,支援的檢查如下:

  • 未知的空值
  • 屬性存取權
  • 沒有硬式 Kotlin 關鍵字
  • 上次 Lambda 參數

Android Studio

如要啟用這些檢查功能,請前往檔案 >偏好設定 >編輯器 >檢查 在 Kotlin 互通性下,檢查要啟用的規則:

圖 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