מדריך לפעולה הדדית ב-Kotlin-Java

קל לארגן דפים בעזרת אוספים אפשר לשמור ולסווג תוכן על סמך ההעדפות שלך.

המסמך הזה הוא קבוצת כללים ליצירת ממשקי API ציבוריים ב-Java וב-Kotlin מתוך כוונה שהקוד ירגיש אידיומטי כשצריך אותו מהצד השני בשפת היעד.

עדכון אחרון: 29.07.2024

Java (לצריכת Kotlin)

אין להשתמש במילות מפתח קשות

אסור להשתמש במילות מפתח קשות של Kotlin כשם של שיטות או שדות. כדי שההודעות האלה יתבצעו, צריך להשתמש בגרשיים (backtics) כדי לסמן בתו בריחה (escape) Kotlin. מילות מפתח רכות , מילות מפתח עם מגבילי התאמה וכן מותר להשתמש במזהים מיוחדים .

לדוגמה, כדי להשתמש בפונקציה when של Mockito צריך להוסיף גרשיים (backtics) כשמשתמשים מ-Kotlin:

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

להימנע מ-Any שמות של תוספים

לא משתמשים בשמות של פונקציות התוספים ב-Any בשביל או השמות של מאפייני התוספים ב-Any עבור אלא אם כן יש בכך צורך. אמנם השיטות והשדות של חברי הקבוצה תמיד יש קדימות על פני הפונקציות או מאפייני התוספים של Any , הם יכולים להיות כשקוראים את הקוד, קשה לדעת לאיזה קוד קוראים קוראים.

הערות לגבי ביטולי null

כל פרמטר, חזרה וסוג שדה לא פרימיטיביים ב-API ציבורי כוללים הערה לגבי יכולת null. סוגים ללא הערות מפורשים בתור "פלטפורמה" , שיכול להיות שהיא לא תהיה ברורה.

כברירת מחדל, דגלי המהדר של Kotlin מכבדים את ההערות של JSR 305 אבל מסמנים אותן עם אזהרות. תוכלו גם להגדיר דגל כדי שהמהדר יטפל בתור שגיאות.

פרמטרים של Lambda אחרונים

סוגי הפרמטרים שעומדים בקריטריונים להמרה ב-SAM צריכים להיות אחרונים.

לדוגמה, חתימת השיטה Flowable.create() ב-RxJava 2 מוגדרת כך:

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

מכיוון ש-FflowableOnSubscribe עומד בדרישות להמרת SAM, קריאות לפונקציות של השיטה הזו מ-Kotlin נראית כך:

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

אבל אם הפרמטרים מתבטלים בחתימת ה-method, הקריאות של הפונקציות יכול להשתמש בתחביר lambda בסוף:

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

תחיליות של נכסים

לשיטה שמיוצגת כנכס ב-Kotlin, שימוש מחמיר בסגנון 'שעועית' חובה להשתמש בקידומת.

ל-methods של Access צריך להוסיף קידומת get , או ל-methods עם החזרה בוליאנית 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()

ל-methods משויכות נדרשת קידומת 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)

אם אתם רוצים ש-methods יהיו חשופות כמאפיינים, אל תשתמשו בקידומות לא סטנדרטיות כמו 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

ניתן להטמיע ממשקי method יחידה (SAM) המוגדרים ב-Java ב-Kotlin ו-Java באמצעות תחביר lambda, שמטביע את ההטמעה בדרך הזו. ב-Kotlin יש כמה אפשרויות להגדרת ממשקים כאלה, הבדל.

הגדרה מועדפת

פונקציות בסדר גבוה יותר שאמורות להשתמש בהן מ-Java אי אפשר להשתמש בסוגי פונקציות שמחזירים Unit כמו שצריך לדרוש שמתקשרים ב-Java יחזירו Unit.INSTANCE . במקום להטמיע את הפונקציה בחתימה, להשתמש בממשקים פונקציונליים (SAM) . וגם כדאי להשתמש בממשקים פונקציונליים (SAM) במקום בממשקים רגילים כאשר מגדירים ממשקים שצפויים לשמש כ-lambdas, שמאפשר שימוש אידיומטי מ-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 הוא עדיין יכול להיות טוב להפוך אותו לממשק בעל שם כדי לאפשר למתקשרים ליישם אותו עם class ולא רק lambdas (ב-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 לא הגיוני. דוגמה בולטת היא דומה : כי היא אמורה להשוות בין this ל-other , ולקבוצות lambda אין this . לא הוספת קידומת לממשק ב-fun מאלצת את המתקשר/ת להשתמש ב-object : ... תחביר, שמאפשר לכלול מצב שמספק רמז לקורא.

נבחן את ההגדרה הבאה של Kotlin:

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

הוא מונע תחביר lambda ב-Kotlin, מה שמחייב את הגרסה הארוכה יותר הזו:

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

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

הימנעות משימוש כללי בNothing

סוג שהפרמטר הגנרי שלו הוא Nothing נחשף כסוגים גולמיים ל-Java. גולמי ב-Java נעשה שימוש רק לעיתים רחוקות, ויש להימנע מהם.

חריגים במסמך

פונקציות שיכולות להשליך על חריגות בבדיקה צריכות לתעד אותן באמצעות @Throws יש לתעד חריגים בסביבת זמן הריצה ב-KDoc.

חשוב לשים לב לממשקי ה-API שהפונקציה מאצילה אליהם, כי הם עלולים לבדוק חריגים ש-Kotlin מאפשר הפצה באופן עצמאי.

עותקים הגנתיים

כשמחזירים אוספים לקריאה בלבד ששותפו או שאינם בבעלותם מממשקי API ציבוריים, צריך לשמור על אריזה אותם בקונטיינר שלא ניתן לשינוי או לבצע עותק הגנתי. למרות Kotlin אוכפים את נכס לקריאה בלבד, אין אכיפה כזו ב-Java. צד שלישי. ללא wrapper או עותק הגנתי, אפשר להפר משתנים קבועים באמצעות החזרת הפניה לאוסף לטווח ארוך.

פונקציות נלוות

חובה להוסיף הערות לפונקציות ציבוריות באובייקט נלווה באמצעות @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 כדי להיחשף כשדה סטטי.

בלי ההערה, המאפיינים האלה זמינים רק עם שמות מוזרים למשל 'getters' בשדה Companion הסטטי. שימוש ב-@JvmStatic במקום זאת של @JvmField מעביר את השם המוזר של 'getters' ל-methods סטטיות בכיתה, וזה עדיין שגוי.

שגוי: אין הערה

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 שיעזרו לכם לזהות ולסמן חלק שתוארו קודם לכן על בעיות ביכולת הפעולה ההדדית. רק בעיות ב-Java (עבור Kotlin צריכה). באופן ספציפי, הבדיקות הנתמכות הן:

  • ערך אפסי לא ידוע
  • גישה לנכס
  • אין מילות מפתח קשיחות ב-Kotlin
  • פרמטרים של Lambda אחרונים

סטודיו ל-Android

כדי להפעיל את הבדיקות האלה, יש לעבור אל קובץ > העדפות > עריכה > בדיקות וגם כדי לבדוק את הכללים שרוצים להפעיל במסגרת יכולת הפעולה ההדדית של Kotlin:

איור 1. הגדרות יכולת הפעולה ההדדית של Kotlin ב-Android Studio.

אחרי שבדקת את הכללים שברצונך להפעיל, הבדיקות החדשות פועל כאשר אתה מריץ בדיקות קוד (ניתוח > בדיקת קוד... )

פיתוח גרסאות build של שורת הפקודה

כדי להפעיל את הבדיקות האלה דרך גרסאות ה-build של שורת הפקודה, צריך להוסיף את השורה הבאה קובץ build.gradle שלך:

מגניב

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

כדי לראות את רשימת ההגדרות המלאה שנתמכות ב-lintOptions, אפשר לעיין חומר עזר בנושא Gradle DSL .

לאחר מכן, מריצים את הפקודה ./gradlew lint משורת הפקודה.

דוגמאות התוכן והקוד שבדף הזה כפופות לרישיונות המפורטים בקטע רישיון לתוכן .‏ Java ו-OpenJDK הם סימנים מסחריים או סימנים מסחריים רשומים של חברת Oracle ו/או של השותפים העצמאיים שלה.

עדכון אחרון: 2024-08-29 (שעון UTC).