«أنواع غير متوافقة»: لماذا يصرخ Kotlin على قائمتك؟
\n\nهل سبق لك أن واجهت موقفًا حيث Kotlin، هذه اللغة «الذكية» و«الآمنة»، تصدر فجأة تحذيرًا أصفر Unchecked Cast: kotlin.collections.List<Kotlin.Any?> to kotlin.collections.List<Waypoint>؟ إذا كان الأمر كذلك، فأنت لست وحدك. هذا السؤال على Stack Overflow حصد آلاف المشاهدات، وهذا ليس مفاجئًا. كل من ينتقل من Java أو يبدأ العمل بعمق مع الأدوية العامة (Generics) في Kotlin، عاجلاً أم آجلاً، يصطدم بهذا «المشكلة».
في هذه المقالة، لن نقدم مجرد شرح جاف. سنحلل لماذا يحدث هذا، ونعرض ثلاث طرق لحل المشكلة بشكل صحيح، والأهم من ذلك - كيفية كتابة كود لا ينفجر في وقت التشغيل. هل أنت مستعد؟ لنبدأ!
\n\nجوهر المشكلة: محو النوع (Type Erasure)
\n\nدعنا نذهب مباشرة إلى صلب الموضوع. تخيل أن لديك دالة تُرجع قائمة من أي شيء - List<Any?>. لكنك تحتاج إلى قائمة من كائنات محددة، على سبيل المثال، List<Waypoint>. الحل «الأبسط» الذي يتبادر إلى الذهن هو التحويل الصريح:
val rawList: List<Any?> = getDataFromSomewhere() // لنفترض أنها JSON أو قاعدة بيانات\nval waypointList: List<Waypoint> = rawList as List<Waypoint> // Unchecked Cast! ⚠️\n\nيستخدم Kotlin (و Java تحت الغطاء) آلية محو النوع (Type Erasure). هذا يعني أنه أثناء تنفيذ البرنامج، يتم محو معلومات النوع داخل الأدوية العامة (ما بين الأقواس الزاوية). بالنسبة لـ JVM، فإن List<Waypoint> الخاص بك هو مجرد List. يرى المترجم أنك تحاول خداع النظام ويصدر تحذيرًا: «مرحبًا، لا يمكنني التحقق من وجود Waypoint بالفعل، أنت تتحمل المسؤولية!».
إذا كنت متأكدًا بنسبة 100% أن الداخل هو Waypoint فقط، يمكنك كتم التحذير باستخدام التعليق التوضيحي @Suppress("UNCHECKED_CAST"). ولكن إذا كان هناك، على سبيل المثال، String - فستحصل على ClassCastException في وقت التشغيل. وهذا هو بالضبط ما يحاول Kotlin حمايتنا منه.
ما لا يجب فعله: أخطاء المبتدئين الشائعة
\n\nإليك بعض الأمثلة التي أراها كثيرًا في المنتديات. تذكرها لتجنب تكرارها:
\n- \n
- التحويل الأعمى:
list as List<Waypoint>- الخيار الأكثر خطورة. \n - تجاهل التحذير: «إنه يعمل!» - يعمل حتى يسقط. \n
- محاولة التحويل في سلسلة:
(list as? List<Waypoint>)- العامل الآمنas?لن يساعد إذا كانت العناصر داخل القائمة ليست من النوع الصحيح. سيتحقق فقط من أنهاList، وليس من محتواها. \n
3 طرق عملية: من البسيط إلى الاحترافي
\n\nدعنا نلقي نظرة على أمثلة كود حقيقية. لنفترض أن لدينا كلاس Waypoint:
data class Waypoint(val latitude: Double, val longitude: Double, val name: String)\n\nولدينا قائمة «غير نظيفة» تم الحصول عليها من API خارجي:
\n\nfun fetchRawData(): List<Any?> {\n return listOf(\n mapOf("lat" to 55.75, "lon" to 37.62, "name" to "Moscow"),\n mapOf("lat" to 59.93, "lon" to 30.31, "name" to "SPb")\n )\n}\n\nالطريقة 1: التصفية باستخدام filterIsInstance (موصى بها ✅)
\n\nهذه هي الطريقة الأكثر اصطلاحية وأمانًا. الدالة filterIsInstance<T>() ستصفي فقط العناصر التي هي نسخ من النوع المحدد، وستعيد قائمة من النوع المطلوب.
fun getWaypointsSafely(): List<Waypoint> {\n val rawData: List<Any?> = fetchRawData()\n // لنفترض أننا بطريقة ما نحول Map إلى Waypoint\n val waypoints: List<Waypoint> = rawData\n .filterIsInstance<Map<*, *>>()\n