هذا المستند عبارة عن مجموعة من القواعد لإنشاء واجهات برمجة التطبيقات العامة في 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
، إلا أنّه قد يكون من الصعب عند قراءة الرمز البرمجي معرفة أيهما يتم استدعاؤه.
التعليقات التوضيحية التي لا تتضمّن أي قيم
يجب أن يحتوي كل مَعلمة غير مبدئية وإرجاع ونوع حقل في واجهة برمجة تطبيقات عامة على تعليق توضيحي بشأن القيم الفارغة. يتم تفسير الأنواع التي لا تحتوي على تعليقات توضيحية على أنها أنواع"platform"، والتي لها قيمة غامضة للقيم الفارغة.
تلتزم علامات المحول البرمجي لـ Kotlin تلقائيًا بالتعليقات التوضيحية JSR 305 ولكنها تضع تحذيرات عليها. يمكنك أيضًا تعيين علامة لجعل المحول البرمجي يتعامل مع التعليقات التوضيحية على أنها أخطاء.
تاريخ آخر معلمات Lambda
يجب أن تكون أنواع المَعلمات المؤهَّلة للإحالة ناجحة في SAM الأخيرة.
على سبيل المثال، يتم تعريف توقيع طريقة RxJava 2’s Flowable.create()
على النحو التالي:
public staticFlowable 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 Optionaldata 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"; OptionaloptionalString = 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":
بعد التحقّق من القواعد التي تريد تفعيلها، سيتم تنفيذ عمليات التحقّق الجديدة عند إجراء عمليات فحص الرمز (تحليل > فحص الرمز...).
إصدارات سطر الأوامر
لتفعيل عمليات التحقّق هذه من إصدارات سطر الأوامر، أضِف السطر التالي في ملف build.gradle
:
رائع
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
للحصول على المجموعة الكاملة من الإعدادات المتوافقة داخل lintOptions، يُرجى الرجوع إلى مرجع Gradle DSL لنظام التشغيل Android.
بعد ذلك، شغِّل ./gradlew lint
من سطر الأوامر.