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

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

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

Java (لاستهلاك لغة Kotlin)

ما مِن كلمات رئيسية صعبة

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

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

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

تجنُّب استخدام أسماء Any للإضافات

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

التعليقات التوضيحية التي لا تتضمّن أي قيم

يجب أن يحتوي كل مَعلمة غير مبدئية وإرجاع ونوع حقل في واجهة برمجة تطبيقات عامة على تعليق توضيحي بشأن القيم الفارغة. يتم تفسير الأنواع التي لا تحتوي على تعليقات توضيحية على أنها أنواع"platform"، والتي لها قيمة غامضة للقيم الفارغة.

تلتزم علامات المحول البرمجي لـ Kotlin تلقائيًا بالتعليقات التوضيحية JSR 305 ولكنها تضع تحذيرات عليها. يمكنك أيضًا تعيين علامة لجعل المحول البرمجي يتعامل مع التعليقات التوضيحية على أنها أخطاء.

تاريخ آخر معلمات Lambda

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

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

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

نظرًا لأن FlowableOnSubscribe مؤهل لتحويل 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)

إذا أردت عرض الطرق كسمات، لا تستخدِم بادئات غير عادية، مثل الموصّلات التي لا تبدأ بـ "تحتوي على"/"ضبط" أو لا. تظل الطرق ذات البادئات غير العادية قابلة للاستدعاء كدوال قد تكون مقبولة استنادًا إلى سلوك الطريقة.

الحِمل الزائد على عامل التشغيل

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

وسيطات لامدا

يمكن تنفيذ الواجهات الأحادية الطريقة (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!") }

عند الاستدعاء من جافا:

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

تجنَّب الواجهات الوظيفية عندما يكون من المفترض أن تحصل عملية التنفيذ على حالة.

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

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

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

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

فحوصات الوبر

الشروط

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

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

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

  • قيمة فارغة غير معروفة
  • الدخول إلى الممتلكات
  • لا تتوفّر كلمات رئيسية ثابتة في لغة البرمجة Kotlin.
  • آخر معلمات Lambda

استوديو Android

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

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

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

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

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

رائع

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

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

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