* و Any في Kotlin: ما الفرق وماذا تختار؟ تحليل مع أمثلة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا الخلط بين العلامة النجمية و Any أمر طبيعي

\n\n

إذا كنت تكتب بلغة Kotlin لمدة أسبوعين على الأقل، فمن المحتمل أنك واجهت الدجينيريكس (Generics). وإذا كنت قد ألقيت نظرة على الكود المصدري للمكتبة القياسية أو كتبت شيئًا مثل List<*>، فربما تساءلت: "لماذا لا أكتب ببساطة List<Any>؟ أليست العلامة النجمية تعني "أي نوع"؟"

\n\n

هذا السؤال هو من أكثر الأسئلة شيوعًا على Stack Overflow (آلاف المشاهدات). وليس من المستغرب: للوهلة الأولى، يبدو * و Any قابلين للتبادل. لكن في الواقع، بينهما فجوة كبيرة، وإذا لم تلاحظها، فقد تكتب كودًا لا يتم تجميعه، أو ما هو أسوأ، قد يتعطل بصمت أثناء وقت التشغيل.

\n\n

في هذه المقالة، سنوضح مرة واحدة وإلى الأبد الفرق بين * (star projection) و Any في دجينيريكس Kotlin. ستتعرف على متى تستخدم كل منهما، وكيف تعمل التباينية (Variance)، ولماذا العلامة النجمية ليست "Any"، بل "لا أعرف ما هو النوع، لكن لا يهمني". هيا بنا!

\n\n

نبذة عن الدجينيريكس: لماذا نحتاجها أساسًا

\n\n

تسمح لنا الدجينيريكس (التعميمات) بكتابة كلاسيات ودوال تعمل مع أنواع مختلفة دون فقدان أمان النوع. بدلاً من كتابة كلاس منفصل لـ Int وآخر لـ String وآخر لـ User، نكتب كلاسًا واحدًا Box<T>.

\n\n

لكن ماذا لو أردنا كتابة دالة تقبل أي Box، بغض النظر عما بداخله؟ هنا يظهر بطلا قصتنا: * و Any.

\n\n

Any هو نوع محدد (يشبه Object تقريبًا)

\n\n

في Kotlin، Any هو جذر التسلسل الهرمي للأنواع لجميع الأنواع غير القابلة للصفر (non-null). إذا كتبت List<Any>، فهذا يعني: "قائمة تحتوي على عناصر من النوع Any". أي يمكنك وضع Int و String و User فيها — أي شيء، لأنها جميعًا ترث من Any.

\n\n

نقطة مهمة: List<Any> هو نوع محدد بمعاملات. المترجم يعلم أن نوع العنصر هو Any.

\n\n
val listOfAny: MutableList<Any> = mutableListOf(1, "hello", 3.14)\nlistOfAny.add(42) // OK\nlistOfAny.add("world") // OK\n
\n\n

هنا كل شيء متوقع: لقد قلنا بوضوح أن القائمة تعمل مع Any. لكن هناك فارق بسيط: MutableList<Any> ليست MutableList<String>. لا يمكنك تمرير قائمة من السلاسل النصية إلى مكان يُتوقع فيه قائمة من Any (إلا إذا كنت تستخدم التباينية المشتركة (Covariance)، ولكن سنتحدث عن ذلك لاحقًا).

\n\n

العلامة النجمية (*) هي "لا أعرف، لكن لا يهمني"

\n\n

العلامة النجمية، أو star projection، هي قصة مختلفة تمامًا. عندما تكتب List<*>، فأنت تقول للمترجم: "هذه قائمة من نوع ما، لكنني لا أعرف أي نوع بالضبط، ولا يهمني". هذا إسقاط لنوع غير معروف.

\n\n

الفرق الرئيسي: * ليس نوعًا، بل wildcard (علامة استبدال). يعني أن النوع غير معروف، ويضع المترجم قيودًا على ما يمكنك فعله بهذا الكائن.

\n\n
fun printLength(list: List<*>) {\n    // list.add(1) // خطأ في التجميع! لا يمكن إضافة عنصر\n    println(list.size) // OK: يمكن معرفة الحجم\n    for (item in list) {\n        println(item) // OK: نوع item هو Any?\n    }\n}\n
\n\n

لاحظ: عندما تقرأ العناصر من List<*>، فإن نوعها هو Any? (لأن القائمة قد تحتوي على null). لكن لا يمكنك إضافة عناصر، لأن المترجم لا يعرف النوع الحقيقي المخزن فيها. هذه حماية ضد انتهاك أمان النوع.

\n\n

الفرق الرئيسي في كلمتين

\n\n
    \n
  • List<Any> — قائمة يمكن أن تحتوي

المدونات

توصيات الكتب