دليل إمكانية التشغيل التفاعلي بلغة Kotlin-Java

هذا المستند عبارة عن مجموعة قواعد لتأليف واجهات برمجة تطبيقات عامة في جافا وكوتلن بقصد أن تكون الشفرة مفهومة عند استخدامها من اللغة الأخرى.

تاريخ آخر تعديل: 18-05-2018

جافا (للغة Kotlin)

لا كلمات رئيسية قوية

لا تستخدم أيًّا من الكلمات الرئيسية المشددة بلغة Kotlin كاسم للطرق أو الحقول. وتتطلب هذه الإجراءات استخدام علامات التجزئة للهروب عند الاتصال من Kotlin. يُسمح باستخدام الكلمات الرئيسية الخفيفة والكلمات الرئيسية المعدِّلة والمعرّفات الخاصة.

على سبيل المثال، تتطلّب الدالة when في Mockito فواصل عليا عند استخدامها من لغة Kotlin:

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

تجنب Any إضافة

تجنَّب استخدام أسماء دوال الإضافات في Any للطرق أو أسماء سمات الإضافة في Any للحقول ما لم تكن هناك أسماء. ضروري للغاية. على الرغم من أن طرق وحقول الأعضاء ستكون لها دائمًا الأسبقية على وظائف إضافات Any أو خصائصها، إلا أنه قد يكون من الصعب قراءة أي رمز يتم طلبه عند قراءة الرمز.

التعليقات التوضيحية للإبطال

يجب أن تتضمّن كل معلّمة بدائية وخاصية ونوع حقل في واجهة برمجة تطبيقات عامة تعليقًا توضيحيًا للقيم الفارغة. يتم تفسير الأنواع غير المزوَّدة بتعليقات توضيحية باعتبارها أنواع"وسيط عرض الإعلان"، والتي تتسم بقيم غامضة فارغة.

بشكل افتراضي، فإن علامات المحول البرمجي بلغة Kotlin تحترم التعليقات التوضيحية لـ JSR 305 ولكنها تضع علامة عليها تشير إلى وجود تحذيرات. يمكنك أيضًا تعيين علامة لجعل برنامج التجميع يتعامل مع التعليقات التوضيحية كأخطاء.

معلمات لامدا أخيرة

يجب أن تكون أنواع المعلمات المؤهّلة للإحالات الناجحة لـ SAM هي الأخيرة.

على سبيل المثال، يتم تعريف توقيع طريقة RxJava 2’s Flowable.create() على النحو التالي:

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

نظرًا لأن FlowableOnSignup مؤهل لتحويل SAM، فإن استدعاءات الوظائف بهذه الطريقة من Kotlin تبدو كما يلي:

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

ومع ذلك، إذا تم عكس المعلمات في توقيع الطريقة، فمن الممكن أن تستخدم استدعاءات الدالة بنية لاحقة lambda:

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

بادئات المواقع

يجب استخدام بادئة صارمة على غرار "الفاصولياء" لطريقة يتم تمثيلها كخاصية في لغة Kotlin.

تتطلّب طرق الموصّلات بادئة "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 (لاستهلاك جافا)

اسم الملف

عندما يحتوي ملف على وظائف أو خصائص عالية المستوى، يمكنك التعليق عليه دائمًا باستخدام @file:JvmName("Foo") لتوفير اسم لطيف.

سيتم تلقائيًا نقل أعضاء المستوى الأعلى في ملف MyClass.kt إلى صف باسم MyClassKt لا يُعجبهم ذلك ويُسرّب اللغة في تفاصيل التنفيذ.

يمكنك إضافة @file:JvmMultifileClass لدمج أعضاء المستوى الأعلى من ملفات متعددة في صف واحد.

وسيطات lambda

يجب أن تتجنّب أنواع الدوال المُراد استخدامها من جافا نوع العرض Unit. ويتطلّب ذلك تحديد عبارة صريحة من return Unit.INSTANCE; غير متّسقة.

fun sayHi(callback: (String) -> Unit) = /* … */
// Kotlin caller:
greeter.sayHi { Log.d("Greeting", "Hello, $it!") }
// Java caller:
greeter.sayHi(name -> {
    Log.d("Greeting", "Hello, " + name + "!");
    return Unit.INSTANCE;
});

ولا تسمح هذه البنية أيضًا بتوفير نوع مُسمى دلاليًا بحيث يمكن تنفيذه على أنواع أخرى.

يؤدي تحديد واجهة طريقة مجردة ذات اسم واحد (SAM) باللغة Kotlin لنوع lambda إلى إصلاح المشكلة المتعلقة بلغة Java، ولكنه يمنع استخدام بنية lambda في Kotlin.

interface GreeterCallback {
    fun greetName(name: String): Unit
}

fun sayHi(callback: GreeterCallback) = /* … */
// Kotlin caller:
greeter.sayHi(object : GreeterCallback {
    override fun greetName(name: String) {
        Log.d("Greeting", "Hello, $name!")
    }
})
// Java caller:
greeter.sayHi(name -> Log.d("Greeting", "Hello, " + name + "!"))

يؤدي تحديد واجهة SAM مُعنونة في جافا إلى استخدام إصدار أقل قليلاً من بنية Kotlin lambda حيث يجب تحديد نوع الواجهة بشكل صريح.

// Defined in Java:
interface GreeterCallback {
    void greetName(String name);
}
fun sayHi(greeter: GreeterCallback) = /* … */
// Kotlin caller:
greeter.sayHi(GreeterCallback { Log.d("Greeting", "Hello, $it!") })
// Java caller:
greeter.sayHi(name -> Log.d("Greeter", "Hello, " + name + "!"));

وفي الوقت الحالي، ليست هناك طريقة لتحديد نوع معلمة لاستخدامه كدالة lambda من كلٍّ من جافا وكوتلين بحيث تبدو اصطلاحية من كلتا اللغتين. الاقتراح الحالي هو تفضيل نوع الدالة على الرغم من انخفاض مستوى التجربة من جافا عندما يكون نوع العرض Unit.

تجنّب أدوية Nothing عامة

يتم عرض النوع الذي يكون المُعلَّمة العامة فيه Nothing كأنواع أولية للغة جافا. نادرًا ما تُستخدم أنواع البيانات الأولية في جافا ويجب تجنبها.

استثناءات المستندات

يجب توثيق الدوال التي يمكنها عرض استثناءات محدّدة باستخدام @Throws. يجب توثيق استثناءات وقت التشغيل في KDoc.

انتبِه إلى واجهات برمجة التطبيقات التي تفوض لها الدالة حيث إنها قد تطرح استثناءات تم تحديدها، وهو ما يسمح لغة Kotlin بنشره بدون تنبيه.

النُسخ الدفاعية

عند عرض مجموعات للقراءة فقط مملوكة أو مشتركة من واجهات برمجة تطبيقات عامة، يمكنك لفّها في حاوية غير قابلة للتعديل أو تنفيذ نسخة دفاعية. على الرغم من أن Kotlin يفرض ملكية خاصة بها للقراءة فقط، فليس هناك مثل هذا التنفيذ من جانب جافا. وبدون استخدام الغلاف أو النسخة الدفاعية، يمكن انتهاك الثوابت من خلال إرجاع مرجع مجموعة قديم.

الوظائف المصاحبة

يجب التعليق التوضيحي للدوال العامة في كائن مصاحب باستخدام @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 إلى نقل "المتغيّرين" ذوي الأسماء الغريبة إلى الطرق الثابتة في الصف، إلا أنّ ذلك لا يزال غير صحيح.

غير صحيح: بلا تعليق توضيحي

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
data class Some(val value: T): Optional()
object None : Optional()

@JvmName("ofNullable")
fun  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 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": 3.2 Canary 10 أو الإصدارات الأحدث
  • إصدار المكون الإضافي Android Gradle: 3.2 أو الأحدث

الشيكات المعتمدة

تتوفّر حاليًا عمليات تحقّق من Android Lint لمساعدتك على رصد بعض مشاكل إمكانية التشغيل التفاعلي الموضّحة أعلاه والإبلاغ عنها. لا يتم حاليًا رصد سوى مشاكل جافا (لاستهلاك لغة Kotlin). على وجه التحديد، عمليات التحقق المتوافقة هي:

  • اسم عارٍ غير معروف
  • الدخول إلى الممتلكات
  • لا توجد كلمات رئيسية بلغة Kotlin قوية
  • معلمات لامدا أخيرة

استوديو Android

لتفعيل عمليات التحقّق هذه، انتقِل إلى ملف > الإعدادات المفضّلة > محرّر > عمليات الفحص وتحقّق من القواعد التي تريد تفعيلها ضمن إمكانية التشغيل التفاعلي Kotlin:

الشكل 1. إعدادات إمكانية التشغيل التفاعلي في Kotlin في "استوديو Android"

بعد التحقّق من القواعد التي تريد تفعيلها، سيتم تنفيذ عمليات التحقّق الجديدة عند تشغيل عمليات فحص الرمز (التحليل > فحص الرمز...)

إصدارات سطر الأوامر

لتفعيل عمليات التحقق هذه من إصدارات سطر الأوامر، أضِف السطر التالي في ملف build.gradle:

رائع

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

لغة Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

للحصول على المجموعة الكاملة من التهيئات المتوافقة داخل lintOptions، راجع مرجع Gradle DSL لنظام التشغيل Android.

بعد ذلك، شغِّل ./gradlew lint من سطر الأوامر.