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

هذا المستند هو مجموعة من القواعد الخاصة بإنشاء واجهات برمجة تطبيقات عامة بلغتَي Java وKotlin بهدف أن يبدو الرمز البرمجي مناسبًا عند استخدامه من اللغة الأخرى.

Java (للاستخدام في 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 التوضيحية، ولكنها تضع علامات عليها مع تحذيرات. يمكنك أيضًا ضبط علامة لجعل المحول البرمجي يتعامل مع التعليقات التوضيحية كأخطاء.

مَعلمات Lambda في النهاية

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

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

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

بما أنّ FlowableOnSubscribe مؤهَّلة للتحويل إلى SAM، تبدو استدعاءات الدالة لهذه الطريقة من Kotlin على النحو التالي:

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

إذا تم عكس ترتيب المَعلمات في توقيع الطريقة، يمكن استخدام صيغة trailing-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)

إذا كنت تريد عرض الطرق كخصائص، لا تستخدِم بادئات غير عادية، مثل 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

يمكن تنفيذ واجهات الطُرق الفردية (SAM) المحدّدة في Java في كلّ من Kotlin و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 هو مثال بارز، لأنّه يهدف إلى مقارنة 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.

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

نُسخ دفاعية

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

الدوال المصاحبة

يجب إضافة التعليق التوضيحي @JvmStatic إلى الدوال العامة في كائن مصاحب ليتم عرضها كطريقة ثابتة.

بدون التعليق التوضيحي، لا تتوفّر هذه الدوال إلا كطُرق مثيل في حقل Companion ثابت.

غير صحيح: ما مِن تعليق توضيحي

class KotlinClass {
    companion object {
        fun doWork() {
            /* … */
        }
    }
}
public final class JavaClass {
    public static void main(String... args) {
        KotlinClass.Companion.doWork();
    }
}

صحيح: @JvmStatic annotation

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

غير صحيح: ما مِن تعليق توضيحي

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 annotation

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 annotation.

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

عمليات التحقّق من الأخطاء البرمجية

المتطلبات

  • إصدار &quot;استوديو Android&quot;: الإصدار 3.2 Canary 10 أو إصدار أحدث
  • إصدار المكوّن الإضافي لنظام Gradle المتوافق مع Android: 3.2 أو إصدار أحدث

عمليات التحقّق المتوافقة

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

  • قيمة Null غير معروفة
  • الدخول إلى الممتلكات
  • ما مِن كلمات رئيسية صعبة في Kotlin
  • مَعلمات Lambda في النهاية

استوديو Android

لتفعيل عمليات التحقّق هذه، انتقِل إلى ملف (File) > الإعدادات المفضّلة (Preferences) > المحرّر (Editor) > عمليات الفحص (Inspections)، ثم ضَع علامة في مربّع الاختيار بجانب القواعد التي تريد تفعيلها ضمن "التوافق مع Kotlin" (Kotlin Interoperability):

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

بعد وضع علامة في مربّع القواعد التي تريد تفعيلها، سيتم تنفيذ عمليات التحقّق الجديدة عند إجراء عمليات فحص الرمز البرمجي (Analyze > Inspect Code…‎).

عمليات الإنشاء من سطر الأوامر

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

أنيق

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

للاطّلاع على المجموعة الكاملة من عمليات الضبط المتوافقة داخل lintOptions، راجِع مرجع لغة DSL لنظام Gradle المتوافق مع Android.

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