راهنمای interop Kotlin-Java

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

جاوا (برای استفاده با کاتلین)

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

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

برای مثال، تابع when در Mockito در صورت استفاده از کاتلین نیاز به علامت‌های برگشتی دارد:

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

Any نام افزونه خودداری کنید

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

حاشیه‌نویسی‌های نال‌پذیری

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

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

پارامترهای لامبدا آخرین

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

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

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

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

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

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

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

پیشوندهای ملکی

برای اینکه یک متد در کاتلین به عنوان یک ویژگی نمایش داده شود، باید از پیشوندگذاری دقیق به سبک "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 یا non- get -prefixed accessors استفاده نکنید. متدهایی که پیشوندهای غیر استاندارد دارند، همچنان به عنوان تابع قابل فراخوانی هستند که بسته به رفتار متد، ممکن است قابل قبول باشد.

سربارگذاری عملگر

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

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 برگردانند. به جای inline کردن نوع تابع در امضا، از رابط‌های تابعی (SAM) استفاده کنید. همچنین هنگام تعریف رابط‌هایی که انتظار می‌رود به عنوان لامبدا استفاده شوند، استفاده از رابط‌های تابعی (SAM) را به جای رابط‌های معمولی در نظر بگیرید، که امکان استفاده اصطلاحی از کاتلین را فراهم می‌کند.

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

fun interface GreeterCallback {
  fun greetName(String name)
}

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

وقتی از کاتلین فراخوانی می‌شود:

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

وقتی از جاوا فراخوانی می‌شود:

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

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

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

وقتی قرار است پیاده‌سازی شامل حالت باشد، از رابط‌های تابعی اجتناب کنید

وقتی قرار است پیاده‌سازی رابط دارای وضعیت (state) باشد، استفاده از سینتکس لامبدا منطقی نیست. Comparable یک مثال برجسته است، زیرا قرار است this با other مقایسه کند، و لامبداها this ندارند. عدم استفاده از پیشوند fun برای رابط، فراخواننده را مجبور به استفاده از object : ... می‌کند که به آن اجازه می‌دهد وضعیت (state) داشته باشد و یک راهنمایی برای فراخواننده فراهم می‌کند.

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

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

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

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 حاشیه‌نویسی شوند تا به عنوان یک فیلد استاتیک نمایش داده شوند.

بدون حاشیه‌نویسی، این ویژگی‌ها فقط به عنوان "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);
    }
}

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

کاتلین قراردادهای فراخوانی متفاوتی نسبت به جاوا دارد که می‌تواند نحوه نامگذاری توابع را تغییر دهد. از @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 ، متدهای تولید شده را بررسی کنید تا مطمئن شوید که هر کدام منطقی هستند. اگر اینطور نیست، یک یا هر دوی بازسازی‌های زیر را تا زمان برآورده شدن نتیجه انجام دهید:

  • ترتیب پارامترها را تغییر دهید تا آنهایی که پیش‌فرض‌هایشان به سمت انتها است، ترجیح داده شوند.
  • مقادیر پیش‌فرض را به توابع overload شده دستی منتقل کنید.

نادرست: بدون @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");
    }
}

بررسی پرز

الزامات

  • نسخه اندروید استودیو: ۳.۲ Canary 10 یا بالاتر
  • نسخه افزونه اندروید Gradle: ۳.۲ یا بالاتر

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

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

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

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

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

شکل ۱. تنظیمات قابلیت همکاری کاتلین در اندروید استودیو.

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

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

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

گرووی

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

کاتلین

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

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

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