راهنمای interop Kotlin-Java

این سند مجموعه‌ای از قوانین برای نوشتن APIهای عمومی در جاوا و کاتلین است با این هدف که کد هنگام مصرف از زبان دیگر حالت اصطلاحی به خود بگیرد.

آخرین به روز رسانی: 29-07-2024

جاوا (برای مصرف کاتلین)

بدون کلمات کلیدی سخت

از هیچ یک از کلمات کلیدی سخت کاتلین به عنوان نام روش ها یا فیلدها استفاده نکنید. این موارد نیاز به استفاده از بکتیک برای فرار هنگام تماس از کاتلین دارند. کلمات کلیدی نرم ، کلمات کلیدی اصلاح کننده ، و شناسه های ویژه مجاز هستند.

به عنوان مثال، وقتی از Kotlin استفاده می شود، when Mockito به backtick نیاز دارد:

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

از Any نام پسوند خودداری کنید

از استفاده از نام توابع پسوند در Any برای متدها یا نام ویژگی های پسوند در Any برای فیلدها، مگر اینکه کاملاً ضروری باشد، خودداری کنید. در حالی که روش‌ها و فیلدهای عضو همیشه بر توابع یا ویژگی‌های پسوند Any ارجحیت دارند، هنگام خواندن کد می‌توان فهمید که کدام یک فراخوانی می‌شود.

حاشیه نویسی پوچ پذیری

هر پارامتر غیر ابتدایی، بازگشت و نوع فیلد در یک API عمومی باید حاشیه‌نویسی پوچ‌پذیری داشته باشد. انواع بدون حاشیه‌نویسی به‌عنوان انواع «پلتفرم» تفسیر می‌شوند که دارای قابلیت پوچ‌پذیری مبهم هستند.

به طور پیش‌فرض، پرچم‌های کامپایلر Kotlin به حاشیه‌نویسی‌های JSR 305 احترام می‌گذارد، اما آنها را با اخطار پرچم‌گذاری می‌کند. همچنین می توانید یک پرچم تنظیم کنید تا کامپایلر با حاشیه نویسی ها به عنوان خطا برخورد کند.

پارامترهای لامبدا باقی می مانند

انواع پارامترهای واجد شرایط برای تبدیل SAM باید آخرین باشند.

برای مثال، امضای متد Flowable.create() RxJava 2 به صورت زیر تعریف می شود:

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

از آنجایی که FlowableOnSubscribe برای تبدیل SAM واجد شرایط است، فراخوانی تابع این روش از Kotlin به شکل زیر است:

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

اگر پارامترها در امضای متد معکوس شوند، فراخوانی های تابع می توانند از نحو دنباله-لامبدا استفاده کنند:

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

پیشوندهای دارایی

برای اینکه یک روش به عنوان یک ویژگی در Kotlin نشان داده شود، باید از پیشوند سختگیرانه به سبک "bean" استفاده شود.

روش‌های Accessor به پیشوند 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 یا non get -prefixed استفاده نکنید. روش‌هایی با پیشوندهای غیراستاندارد همچنان به‌عنوان توابع قابل فراخوانی هستند که بسته به رفتار روش ممکن است قابل قبول باشند.

بارگذاری بیش از حد اپراتور

حواستان به نام‌های روشی باشد که به نحو خاص سایت تماس اجازه می‌دهد (مانند بارگذاری بیش از حد اپراتور در 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)

کاتلین (برای مصرف جاوا)

نام فایل

هنگامی که یک فایل حاوی توابع یا ویژگی های سطح بالا است، همیشه آن را با @file:JvmName("Foo") حاشیه نویسی کنید تا یک نام خوب ارائه شود.

به طور پیش‌فرض، اعضای سطح بالای فایل MyClass.kt به کلاسی به نام MyClassKt ختم می‌شوند که جذاب نیست و زبان را به عنوان جزئیات پیاده‌سازی لو می‌دهد.

افزودن @file:JvmMultifileClass برای ترکیب اعضای سطح بالا از چندین فایل در یک کلاس در نظر بگیرید.

استدلال های لامبدا

رابط های روش تک (SAM) تعریف شده در جاوا را می توان در هر دو کاتلین و جاوا با استفاده از نحو لامبدا پیاده سازی کرد، که پیاده سازی را به روشی اصطلاحی خط می دهد. کاتلین چندین گزینه برای تعریف چنین رابط هایی دارد که هر کدام با کمی تفاوت دارند.

تعریف ترجیحی

توابع درجه بالاتری که قرار است از جاوا استفاده شوند، نباید از انواع تابعی استفاده کنند که Unit برمی گرداند، زیرا به فراخواننده های جاوا نیاز دارد تا Unit.INSTANCE را برگردانند. به جای درج کردن نوع تابع در امضا، از رابط های کاربردی (SAM) استفاده کنید. همچنین هنگام تعریف رابط‌هایی که انتظار می‌رود به‌عنوان لامبدا استفاده شوند، به جای رابط‌های معمولی، از رابط‌های کاربردی (SAM) استفاده کنید، که امکان استفاده اصطلاحی از Kotlin را فراهم می‌کند.

این تعریف کاتلین را در نظر بگیرید:

fun interface GreeterCallback {
  fun greetName(String name)
}

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

هنگامی که از Kotlin فراخوانی می شود:

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

هنگام فراخوانی از جاوا:

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

حتی زمانی که نوع تابع Unit بر نمی‌گرداند، باز هم ممکن است ایده خوبی باشد که آن را یک رابط نام‌گذاری کنید تا به تماس‌گیرندگان اجازه دهد آن را با یک کلاس نام‌گذاری‌شده و نه فقط لامبدا (هم در Kotlin و هم در جاوا) پیاده‌سازی کنند.

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

از انواع تابعی که Unit برمی گرداند اجتناب کنید

این تعریف کاتلین را در نظر بگیرید:

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

برای بازگرداندن Unit.INSTANCE به تماس گیرندگان جاوا نیاز دارد:

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

هنگامی که پیاده سازی به حالت حالت باشد، از رابط های کاربردی اجتناب کنید

وقتی قرار است پیاده سازی رابط حالت داشته باشد، استفاده از نحو لامبدا معنی ندارد. Comparable یک مثال برجسته است، زیرا قصد دارد this با other مقایسه کند، و لامبداها this ندارند. عدم پیشوند رابط با fun ، تماس گیرنده را مجبور می کند از object : ... نحو استفاده کند، که به آن اجازه می دهد حالت داشته باشد و یک اشاره به تماس گیرنده ارائه می دهد.

این تعریف کاتلین را در نظر بگیرید:

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

از نحو لامبدا در Kotlin جلوگیری می کند و به این نسخه طولانی تر نیاز دارد:

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

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

از Nothing ژنریک اجتناب کنید

نوعی که پارامتر عمومی آن Nothing است به عنوان انواع خام در معرض جاوا قرار می گیرد. انواع خام به ندرت در جاوا استفاده می شود و باید از آنها اجتناب کرد.

استثناهای سند

توابعی که می توانند استثناهای علامت خورده را ایجاد کنند باید آنها را با @Throws مستند کنند. استثناهای زمان اجرا باید در KDoc مستند شوند.

مراقب API هایی باشید که یک تابع به آنها واگذار می کند زیرا ممکن است استثناهای بررسی شده ای را ایجاد کنند که در غیر این صورت کاتلین بی سر و صدا اجازه انتشار آنها را می دهد.

کپی های دفاعی

هنگام بازگرداندن مجموعه‌های فقط خواندنی به اشتراک گذاشته شده یا غیرمالکی از APIهای عمومی، آنها را در یک ظرف غیرقابل تغییر بپیچید یا یک کپی دفاعی انجام دهید. علیرغم اینکه کاتلین دارایی فقط خواندنی خود را اعمال می کند، چنین اجرایی در سمت جاوا وجود ندارد. بدون لفاف یا کپی دفاعی، می‌توان با بازگرداندن یک مرجع مجموعه با عمر طولانی، تغییرات ثابت را نقض کرد.

توابع همراه

توابع عمومی در یک شیء همراه باید با @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 حاشیه‌نویسی شوند تا به‌عنوان یک میدان استاتیک در معرض دید قرار گیرند.

بدون حاشیه‌نویسی، این ویژگی‌ها فقط به‌عنوان نمونه‌هایی با نام‌های عجیب و غریب در فیلد Companion ثابت در دسترس هستند. استفاده از @JvmStatic به جای @JvmField ، "getters" با نام عجیب را به متدهای ایستا در کلاس منتقل می کند، که هنوز نادرست است.

نادرست: بدون حاشیه

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);
    }
}

نامگذاری اصطلاحی

کاتلین قوانین فراخوانی متفاوتی نسبت به جاوا دارد که می‌تواند نحوه نام‌گذاری توابع را تغییر دهد. از @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");
    }
}

لینت چک ها

الزامات

  • نسخه اندروید استودیو: 3.2 Canary 10 یا بالاتر
  • نسخه پلاگین Android Gradle: 3.2 یا بالاتر

چک های پشتیبانی شده

اکنون بررسی‌های Android Lint وجود دارد که به شما کمک می‌کند برخی از مشکلات قابلیت همکاری که قبلاً توضیح داده شد را شناسایی و پرچم‌گذاری کنید. فقط مشکلات در جاوا (برای مصرف Kotlin) شناسایی می شوند. به طور خاص، چک های پشتیبانی شده عبارتند از:

  • پوچی ناشناخته
  • دسترسی به اموال
  • بدون کلمات کلیدی Hard Kotlin
  • آخرین پارامترهای لامبدا

اندروید استودیو

برای فعال کردن این بررسی‌ها، به File > Preferences > Editor > Inspections بروید و قوانینی را که می‌خواهید در Kotlin Interoperability فعال کنید، بررسی کنید:

شکل 1. تنظیمات قابلیت همکاری Kotlin در Android Studio.

پس از بررسی قوانینی که می‌خواهید فعال شوند، بررسی‌های جدید هنگام اجرای بازرسی کدتان اجرا می‌شوند ( تحلیل > بازرسی کد… )

ساخت های خط فرمان

برای فعال کردن این بررسی ها از بیلدهای خط فرمان، خط زیر را در فایل build.gradle خود اضافه کنید:

شیار

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

کاتلین

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

برای مجموعه کامل پیکربندی های پشتیبانی شده در lintOptions، به مرجع Android Gradle DSL مراجعه کنید.

سپس، ./gradlew lint از خط فرمان اجرا کنید.