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

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

تاريخ التعديل الأخير: 29/07/2024

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) { /* … */ }

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

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

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

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

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

ولتكون طريقة تمثيلها كخاصية في Kotlin، تُستخدم الطريقة الصارمة لـ "Bein" استخدام البادئة.

تتطلب طرق الموصّل البادئة 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) بدلاً من الواجهات العادية الواجهات عند تحديد الواجهات التي من المتوقع أن تُستخدم كـ lambdas، وهو ما يسمح بالاستخدام الاصطلاحي من لغة 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، يبقى من الأفضل جعلها واجهة مُعنوَنة للسماح للمتصلين بتنفيذها باستخدام وليس فقط lambdas (في كل من 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 وبناء الجملة لا معنى له. قابلة للمقارنة هي مثال بارز، لأنها تهدف إلى المقارنة بين 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();
    }
}

الثوابت المصاحبة

يجب إضافة @JvmField إلى الخصائص العامة غير const التي تُعدّ ثوابتًا فعّالة في companion object ليتم عرضها كحقل ثابت.

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

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 تعليق توضيحي.

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 أو إصدار أحدث
  • إصدار المكوّن الإضافي لنظام 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 من سطر الأوامر.