التحويل غير المُفحص (Unchecked Cast) في Kotlin: كيفية تحويل List<Any?> إلى List<Waypoint> بأمان

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

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

ابدأ الدورة

«أنواع غير متوافقة»: لماذا يصرخ Kotlin على قائمتك؟

\n\n

هل سبق لك أن واجهت موقفًا حيث Kotlin، هذه اللغة «الذكية» و«الآمنة»، تصدر فجأة تحذيرًا أصفر Unchecked Cast: kotlin.collections.List<Kotlin.Any?> to kotlin.collections.List<Waypoint>؟ إذا كان الأمر كذلك، فأنت لست وحدك. هذا السؤال على Stack Overflow حصد آلاف المشاهدات، وهذا ليس مفاجئًا. كل من ينتقل من Java أو يبدأ العمل بعمق مع الأدوية العامة (Generics) في Kotlin، عاجلاً أم آجلاً، يصطدم بهذا «المشكلة».

\n\n

في هذه المقالة، لن نقدم مجرد شرح جاف. سنحلل لماذا يحدث هذا، ونعرض ثلاث طرق لحل المشكلة بشكل صحيح، والأهم من ذلك - كيفية كتابة كود لا ينفجر في وقت التشغيل. هل أنت مستعد؟ لنبدأ!

\n\n

جوهر المشكلة: محو النوع (Type Erasure)

\n\n

دعنا نذهب مباشرة إلى صلب الموضوع. تخيل أن لديك دالة تُرجع قائمة من أي شيء - List<Any?>. لكنك تحتاج إلى قائمة من كائنات محددة، على سبيل المثال، List<Waypoint>. الحل «الأبسط» الذي يتبادر إلى الذهن هو التحويل الصريح:

\n\n
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 بالفعل، أنت تتحمل المسؤولية!».

\n\n

إذا كنت متأكدًا بنسبة 100% أن الداخل هو Waypoint فقط، يمكنك كتم التحذير باستخدام التعليق التوضيحي @Suppress("UNCHECKED_CAST"). ولكن إذا كان هناك، على سبيل المثال، String - فستحصل على ClassCastException في وقت التشغيل. وهذا هو بالضبط ما يحاول Kotlin حمايتنا منه.

\n\n

ما لا يجب فعله: أخطاء المبتدئين الشائعة

\n\n

إليك بعض الأمثلة التي أراها كثيرًا في المنتديات. تذكرها لتجنب تكرارها:

\n
    \n
  • التحويل الأعمى: list as List<Waypoint> - الخيار الأكثر خطورة.
  • \n
  • تجاهل التحذير: «إنه يعمل!» - يعمل حتى يسقط.
  • \n
  • محاولة التحويل في سلسلة: (list as? List<Waypoint>) - العامل الآمن as? لن يساعد إذا كانت العناصر داخل القائمة ليست من النوع الصحيح. سيتحقق فقط من أنها List، وليس من محتواها.
  • \n
\n\n

3 طرق عملية: من البسيط إلى الاحترافي

\n\n

دعنا نلقي نظرة على أمثلة كود حقيقية. لنفترض أن لدينا كلاس Waypoint:

\n\n
data class Waypoint(val latitude: Double, val longitude: Double, val name: String)
\n\n

ولدينا قائمة «غير نظيفة» تم الحصول عليها من API خارجي:

\n\n
fun 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>() ستصفي فقط العناصر التي هي نسخ من النوع المحدد، وستعيد قائمة من النوع المطلوب.

\n\n
fun getWaypointsSafely(): List<Waypoint> {\n    val rawData: List<Any?> = fetchRawData()\n    // لنفترض أننا بطريقة ما نحول Map إلى Waypoint\n    val waypoints: List<Waypoint> = rawData\n        .filterIsInstance<Map<*, *>>()\n                

المدونات

توصيات الكتب