
ستتحدث هذه المقالة عن استخدام الإحصائيات في Kotlin.
دعنا نبدأ.
Kotlin ليس لديه ثابت!
جاء ذلك في الوثائق الرسمية.
ويبدو أن هذا يمكن أن ينهي المقالة. لكن دعني ، كيف ذلك؟ بعد كل شيء ، إذا قمت بإدراج رمز Java في ملف Kotlin في Android Studio ، فسيقوم المحول الذكي بعمل السحر ، وتحويل كل شيء إلى رمز باللغة الصحيحة وسيعمل! ولكن ماذا عن التوافق التام مع Java؟
عند هذه النقطة ، أي مطور ، يتعلم عن عدم وجود ثابت في Kotlin ، سيذهب إلى الوثائق والمنتديات للتعامل مع هذه المشكلة. دعونا نجتمع معا ، بعناية وعناية. سأحاول الاحتفاظ بأقل عدد ممكن من الأسئلة بنهاية هذه المقالة.
ما هو ثابت في جافا؟ هناك:
- الحقول الثابتة للفئة
- طرق الطبقة الثابتة
- فئات متداخلة ثابتة
لنقم بتجربة (هذا هو أول ما يتبادر إلى الذهن).
قم بإنشاء فئة Java بسيطة:
public class SimpleClassJava1 { public static String staticField = "Hello, static!"; public static void setStaticValue (String value){ staticField = value; } }
كل شيء سهل هنا: في الفصل نقوم بإنشاء حقل ثابت وطريقة ثابتة. نحن نفعل كل شيء علنا للتجارب مع الوصول من الخارج. نقوم بتوصيل الحقل والطريقة المنطقية.
الآن قم بإنشاء فئة Kotlin فارغة وحاول نسخ جميع محتويات فئة SimpleClassJava1 فيه. نجيب بـ "نعم" على السؤال الناتج عن التحويل ونرى ما حدث:
class SimpleClassKotlin1 { var staticField = "Hello, static!" fun setStaticValue(value: String) { staticField = value } }
يبدو أن هذا ليس بالضبط ما نحتاجه ... للتأكد من ذلك ، سنقوم بتحويل الرمز الفرعي لهذه الفئة إلى كود Java ونرى ما حدث:
public final class SimpleClassKotlin1 { @NotNull private String staticField = "Hello, static!"; @NotNull public final String getStaticField() { return this.staticField; } public final void setStaticField(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.staticField = var1; } public final void setStaticValue(@NotNull String value) { Intrinsics.checkParameterIsNotNull(value, "value"); this.staticField = value; } }
نعم كل شيء تمامًا كما بدا. لا توجد رائحة ثابتة هنا. قطع المحول ببساطة المعدل الثابت في التوقيع ، كما لو لم يكن هناك. تحسبًا لذلك ، سنستنتج فورًا: لا تثق في المحول بشكل أعمى ، فقد يؤدي في بعض الأحيان إلى مفاجآت غير سارة.
بالمناسبة ، قبل ستة أشهر تقريبًا ، كان من الممكن أن يؤدي تحويل رمز جافا نفسه إلى Kotlin إلى نتيجة مختلفة قليلاً. مرة أخرى: كن حذرًا عند التحويل التلقائي!
نحن نجرب أكثر.
نذهب إلى أي فئة على Kotlin ونحاول استدعاء العناصر الثابتة لفئة Java فيه:
SimpleClassJava1.setStaticValue("hi!") SimpleClassJava1.staticField = "hello!!!"
هنا كيف! كل شيء يسمى تمامًا ، حتى الإكمال التلقائي للكود يخبرنا بكل شيء! فضولي جدا.
الآن دعنا ننتقل إلى الجزء الأكثر جوهرية. في الواقع ، قرر مبدعو Kotlin الابتعاد عن الثابت في الشكل الذي اعتدنا على استخدامه. لماذا فعلنا ذلك بالضبط ولن نناقش خلاف ذلك - هناك الكثير من النزاعات والآراء حول هذا الموضوع في الشبكة. سنكتشف فقط كيف نعيش معها. بطبيعة الحال ، لم نحرم فقط من الإحصائيات. يعطينا Kotlin مجموعة من الأدوات التي يمكننا من خلالها تعويض الخسائر. وهي مناسبة للاستخدام الداخلي. والتوافق الكامل الموعود برمز جافا. دعنا نذهب!
الشيء الأسرع والأسهل الذي يمكنك إدراكه والبدء في استخدامه هو البديل الذي يتم تقديمه لنا بدلاً من الأساليب الثابتة - وظائف مستوى الحزمة. ما هذا هذه وظيفة لا تنتمي إلى أي فئة. هذا هو ، هذا النوع من المنطق في فراغ في مكان ما في مساحة الحزمة. يمكننا وصفه في أي ملف داخل الحزمة التي تهمنا. على سبيل المثال ، قم بتسمية هذا الملف JustFun.kt ووضعه في الحزمة
com.example.mytestapplication
package com.example.mytestapplication fun testFun(){
قم بتحويل الرمز البايت لهذا الملف في Java وانظر إلى الداخل:
public final class JustFunKt { public static final void testFun() {
نرى أنه في Java يتم إنشاء فئة يأخذ اسمها في الاعتبار اسم الملف الذي تم وصف الوظيفة فيه ، وتتحول الوظيفة نفسها إلى طريقة ثابتة.
الآن إذا أردنا استدعاء وظيفة
testFun
في Kotlin من فئة (أو نفس الوظيفة) الموجودة في
package com.example.mytestapplication
(أي نفس الحزمة مثل الوظيفة) ، يمكننا ببساطة الوصول إليها بدون حيل إضافية. إذا نسميها من حزمة أخرى ، فيجب علينا الاستيراد ، وهو أمر مألوف بالنسبة لنا وعادة ما ينطبق على الفئات:
import com.example.pavka.mytestapplication.testFun
إذا تحدثنا عن استدعاء الوظيفة t
estFun
من كود Java ، فإننا نحتاج دائمًا إلى استيراد الوظيفة ، بغض النظر عن الحزمة التي نطلق عليها
estFun
:
import static com.example.pavka.mytestapplication.ForFunKt.testFun;
تقول الوثائق أنه في معظم الحالات ، بدلاً من الأساليب الثابتة ، يكفي لنا استخدام وظائف على مستوى الحزمة. ومع ذلك ، في رأيي الشخصي (الذي لا يتطابق مع رأي الجميع) ، فإن طريقة تنفيذ الإحصائيات هذه مناسبة فقط للمشاريع الصغيرة.
اتضح أن هذه الوظائف لا تنتمي بشكل صريح إلى أي فئة. بصريا ، تبدو مكالمتهم كمكالمة لطريقة الفصل (أو والدها) التي نتواجد فيها ، والتي يمكن أن تكون مربكة في بعض الأحيان. حسنًا والشيء الرئيسي - يمكن أن يكون هناك وظيفة واحدة فقط بهذا الاسم في الحزمة. حتى إذا حاولنا إنشاء وظيفة تحمل نفس الاسم في ملف آخر ، فسوف يعطينا النظام خطأ. إذا كنا نتحدث عن المشاريع الكبيرة ، فغالبًا ما يكون لدينا ، على سبيل المثال ، مصانع مختلفة لها طرق ثابتة تحمل نفس الاسم.
دعونا نلقي نظرة على بدائل أخرى لتطبيق الأساليب والحقول الثابتة.
تذكر ما هو المجال الثابت للفصل. هذا حقل فئة ينتمي إلى الفئة التي تم التصريح عنها ، ولكنه لا ينتمي إلى مثيل محدد للفئة ، أي أنه يتم إنشاؤه في مثيل واحد للفصل بأكمله.
يقدم لنا Kotlin لهذه الأغراض استخدام بعض الكيانات الإضافية ، والتي توجد أيضًا في نسخة واحدة. وبعبارة أخرى ، سينجلتون.
يحتوي Kotlin على كلمة أساسية للكائن لإعلان الأغاني الفردية.
object MySingltoneClass {
تتم تهيئة هذه الأشياء بشكل كسول ، أي في وقت الاستدعاء الأول لها.
حسنًا ، هناك أغاني فردية في جافا أيضًا ، أين الإحصائيات؟
بالنسبة لأي فئة في Kotlin ، يمكننا إنشاء كائن مصاحب أو مصاحب. ترتبط مفردة بفئة معينة. يمكن القيام بذلك باستخدام كلمتين رئيسيتين مصاحبتين
companion object
معًا:
class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){
هنا لدينا فئة
SimpleClassKotlin1
، حيث نعلن داخلها كلمة مفردة مع الكلمة الأساسية للكائن وربطها بالكائن الذي يتم تعريفه بداخله بالكلمة الرئيسية المصاحبة. هنا يمكنك الانتباه إلى حقيقة أنه ، على عكس إعلان Singleton السابق (MySingltoneClass) ، لا يشار إلى اسم فئة singleton. إذا تم التصريح عن الكائن كمصاحب ، فلا يجوز له الإشارة إلى اسمه. ثم سيتم تسميته تلقائيًا
Companion
. إذا لزم الأمر ، يمكننا الحصول على مثيل للفئة المصاحبة بهذه الطريقة:
val companionInstance = SimpleClassKotlin1.Companion
ومع ذلك ، يمكن إجراء استدعاء لخصائص وأساليب فئة المرافقين مباشرةً ، من خلال استدعاء للفئة التي يرتبط بها:
SimpleClassKotlin1.companionField SimpleClassKotlin1.companionFun("Hi!")
يبدو بالفعل مثل استدعاء الحقول والفصول الثابتة ، أليس كذلك؟
إذا لزم الأمر ، يمكننا تسمية الفصل المرافق ، ولكن في الواقع نادرًا ما يتم ذلك. من الميزات المثيرة للاهتمام للفصول المصاحبة ، يمكن ملاحظة أنه ، مثل أي فئة عادية ، يمكنه تنفيذ واجهات ، والتي يمكن أن تساعدنا في بعض الأحيان في إضافة المزيد من النظام إلى التعليمات البرمجية:
interface FactoryInterface<T> { fun factoryMethod(): T } class SimpleClassKotlin1 { companion object : FactoryInterface<MyClass> { override fun factoryMethod(): MyClass = MyClass() } }
يمكن للفصل المرافق أن يكون له فئة واحدة فقط. ومع ذلك ، لا يمنعنا أحد من التصريح عن أي عدد من الكائنات الفردية داخل الفصل ، ولكن في هذه الحالة يجب علينا تحديد اسم هذه الفئة بشكل صريح ، وبالتالي ، الإشارة إلى هذا الاسم عند الإشارة إلى حقول وأسلوب هذه الفئة.
بالحديث عن الفئات المعلنة ككائن ، يمكننا القول أنه يمكننا أيضًا إعلان الكائنات المتداخلة فيها ، ولكن لا يمكننا إعلان كائن مصاحب فيها.
حان الوقت للنظر "تحت الغطاء". خذ صفنا البسيط:
class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){ } } object OneMoreObject { var value = 1 fun function(){ } }
الآن فك شفرة بايته في جافا:
public final class SimpleClassKotlin1 { @NotNull private static String companionField = "Hello!"; public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null); public static final class OneMoreObject { private static int value; public static final SimpleClassKotlin1.OneMoreObject INSTANCE; public final int getValue() { return value; } public final void setValue(int var1) { value = var1; } public final void function() { } static { SimpleClassKotlin1.OneMoreObject var0 = new SimpleClassKotlin1.OneMoreObject(); INSTANCE = var0; value = 1; } } public static final class Companion { @NotNull public final String getCompanionField() { return SimpleClassKotlin1.companionField; } public final void setCompanionField(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); SimpleClassKotlin1.companionField = var1; } public final void companionFun(@NotNull String vaue) { Intrinsics.checkParameterIsNotNull(vaue, "vaue"); } private Companion() { }
نحن ننظر إلى ما حدث.
يتم تمثيل خاصية الكائن المصاحب كحقل ثابت لفئتنا:
private static String companionField = "Hello!";
يبدو أن هذا ما أردناه بالضبط. ومع ذلك ، فإن هذا المجال خاص ويتم الوصول إليه من خلال المُدَرِّب والفئران في صفنا المرافق ، والذي يتم تقديمه هنا كفئة
public static final class
، ويتم تقديم مثيله على أنه ثابت:
public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null);
لم تصبح وظيفة companionFun الطريقة الثابتة لفئتنا (ربما لا يجب أن تكون). بقيت وظيفة مفردة تمت تهيئتها في فئة SimpleClassKotlin1. ومع ذلك ، إذا كنت تفكر في ذلك ، فمن المنطقي أن هذا هو نفس الشيء.
مع فئة
OneMoreObject
الوضع مشابه جدًا. تجدر الإشارة فقط إلى أنه ، على عكس الرفيق ، لم ينتقل مجال فئة القيمة إلى فئة
SimpleClassKotlin1
، ولكنه ظل في
OneMoreObject
، ولكنه أصبح أيضًا ثابتًا وتلقى المولد الناتج عن ذلك.
دعونا نحاول فهم كل ما سبق.
إذا أردنا تنفيذ حقول ثابتة أو طرق فئة في Kotlin ، فيجب علينا استخدام الكائن المصاحب المعلن داخل هذه الفئة.
إن استدعاء هذه الحقول والوظائف من Kotlin سيبدو تمامًا تمامًا مثل استدعاء statics في Java. ولكن ماذا لو حاولنا استدعاء هذه الحقول والوظائف في Java؟
يخبرنا الإكمال التلقائي أن المكالمات التالية متاحة:
SimpleClassKotlin1.Companion.companionFun("hello!"); SimpleClassKotlin1.Companion.setCompanionField("hello!"); SimpleClassKotlin1.Companion.getCompanionField();
أي أننا هنا لن نذهب إلى أي مكان من الإشارة مباشرة إلى اسم الرفيق. وفقًا لذلك ، يتم استخدام الاسم الذي تم تعيينه لكائن المرافق الافتراضي هنا. ليست مريحة للغاية ، أليس كذلك؟
ومع ذلك ، جعل منشئو Kotlin من الممكن جعلها تبدو أكثر دراية في Java. وهناك عدة طرق للقيام بذلك.
@JvmField var companionField = "Hello!"
إذا قمنا بتطبيق هذا التعليق التوضيحي على حقل الحقل
companionField
الخاص
companionField
، فعند تحويل كود البايت إلى Java ، فإننا نرى أن الحقل الثابت
companionField
SimpleClassKotlin1 لم يعد خاصًا ، ولكنه عام ، وذهب كل من getter و setter for companionField في فئة
Companion
الثابتة. الآن يمكننا الوصول إلى
companionField
من Java code بالطريقة المعتادة.
الطريقة الثانية هي تحديد مُعدِّل
lateinit
للخصائص الخاصة
lateinit
المصاحب ، الخصائص ذات التهيئة المتأخرة. لا تنس أن هذا ينطبق فقط على خصائص var ، وأن نوعه يجب أن يكون غير فارغ ولا يجب أن يكون بدائيًا. حسنًا ، لا تنس قواعد التعامل مع هذه الخصائص.
lateinit var lateinitField: String
وطريقة أخرى: يمكننا أن نعلن أن خاصية الكائن المرافق ثابتة من خلال تحديد ثابت المعدل. من السهل تخمين أن هذا يجب أن يكون خاصية فال.
const val myConstant = "CONSTANT"
في كل حالة من هذه الحالات ، سيحتوي رمز Java الذي تم إنشاؤه على الحقل الثابت العام المعتاد ، وفي حالة الثابت ، سيكون هذا الحقل نهائيًا أيضًا. بالطبع ، من المفيد أن نفهم أن كل من هذه الحالات الثلاث لها غرضها المنطقي الخاص بها ، وتم تصميم أولها فقط خصيصًا لسهولة الاستخدام مع Java ، والباقي يحصل على هذا "الكعكة" كما لو كان في الحمل.
وتجدر الإشارة بشكل منفصل إلى أنه يمكن استخدام معدل التعديل لخصائص الكائنات والأشياء المصاحبة وخصائص مستوى الحزمة. في الحالة الأخيرة ، نحصل على نفس استخدام وظائف مستوى الحزمة وبنفس القيود. يتم إنشاء كود Java بحقل عام ثابت في الفصل ، والذي يأخذ اسمه في الاعتبار اسم الملف الذي وصفنا فيه الثابت. يمكن أن تحتوي الحزمة على ثابت واحد فقط بالاسم المحدد.
إذا أردنا أن يتم تحويل وظيفة الكائن المصاحب أيضًا إلى طريقة ثابتة عند إنشاء كود Java ، فحينئذٍ نحتاج إلى تطبيق التعليق التوضيحي
@JvmStatic
على هذه الوظيفة.
يجوز أيضًا تطبيق التعليق التوضيحي
@JvmStatic
على خصائص الكائنات المصاحبة (والأشياء
@JvmStatic
فقط). في هذه الحالة ، لن تتحول الخاصية إلى حقل ثابت ، ولكن سيتم إنشاء أداة إحضار ثابتة ومضبط لهذه الخاصية. لفهم أفضل ، انظر إلى فصل Kotlin هذا:
class SimpleClassKotlin1 { companion object{ @JvmStatic fun companionFun (vaue: String){ } @JvmStatic var staticField = 1 } }
في هذه الحالة ، تكون المكالمات التالية صالحة من Java:
int x; SimpleClassKotlin1.companionFun("hello!"); x = SimpleClassKotlin1.getStaticField(); SimpleClassKotlin1.setStaticField(10); SimpleClassKotlin1.Companion.companionFun("hello"); x = SimpleClassKotlin1.Companion.getStaticField(); SimpleClassKotlin1.Companion.setStaticField(10);
المكالمات التالية صالحة من Kotlin:
SimpleClassKotlin1.companionFun("hello!") SimpleClassKotlin1.staticField SimpleClassKotlin1.Companion.companionFun("hello!") SimpleClassKotlin1.Companion.staticField
من الواضح أنه بالنسبة لـ Java ، يجب عليك استخدام أول 3 ، وبالنسبة لـ Kotlin أول 2. بقية المكالمات صالحة فقط.
الآن يبقى لتوضيح الأخير. ماذا عن الطبقات المتداخلة الثابتة؟ كل شيء بسيط هنا - التناظرية لمثل هذه الفئة في Kotlin هي فئة متداخلة منتظمة بدون معدلات:
class SimpleClassKotlin1 { class LooksLikeNestedStatic { } }
بعد تحويل الرمز البايت إلى Java ، نرى:
public final class SimpleClassKotlin1 { public static final class LooksLikeNestedStatic { } }
في الواقع ، هذا ما نحتاجه. إذا كنا لا نريد أن يكون الفصل نهائيًا ، فإننا في كود Kotlin نحدد المعدل المفتوح له. تذكرت هذا فقط في حالة.
أعتقد أنه يمكنك تلخيص. في الواقع ، في Kotlin نفسها ، كما قيل ، لا يوجد ثابت في الشكل الذي اعتدنا على رؤيته. لكن مجموعة الأدوات المقترحة تسمح لنا بتنفيذ جميع أنواع الإحصائيات في كود Java الذي تم إنشاؤه. يتم أيضًا توفير التوافق التام مع Java ، ويمكننا استدعاء الحقول الثابتة وأساليب فئات Java من Kotlin مباشرة.
في معظم الحالات ، يتطلب تنفيذ أحد الإحصائيات في Kotlin بعض الأسطر من التعليمات البرمجية. ربما هذه واحدة من الحالات القليلة ، أو ربما الحالة الوحيدة عندما تحتاج إلى كتابة المزيد في Kotlin. ومع ذلك ، فإنك تعتاد عليه بسرعة.
أعتقد أنه في المشروعات التي تتم فيها مشاركة كود Kotlin و Java ، يمكنك الاقتراب بمرونة من اختيار اللغة المستخدمة. على سبيل المثال ، يبدو لي أن Java أكثر ملاءمة لتخزين الثوابت. ولكن هنا ، كما هو الحال في العديد من الأشياء الأخرى ، يجدر الاسترشاد بالحس السليم وقواعد كتابة التعليمات البرمجية في المشروع.
وفي نهاية المقال ، هذه معلومات. ربما في المستقبل ، سيظل Kotlin يحتوي على معدل ثابت يحل الكثير من المشاكل ويجعل حياة المطورين أسهل ، والرمز أقصر. لقد قمت بهذا الافتراض من خلال إيجاد النص المناسب في الفقرة 17 من
أوصاف ميزة Kotlin .
صحيح ، يعود تاريخ هذه الوثيقة إلى مايو 2017 ، وفي الساحة بالفعل نهاية عام 2018.
هذا كل شيء بالنسبة لي. أعتقد أنه تم فرز الموضوع ببعض التفاصيل. تكتب الأسئلة في التعليقات.