لماذا يكسر الترتيب "البسيط" في Kotlin الكود؟
\nمرحباً يا زميل! إذا كنت قد كتبت يوماً ما في Kotlin وحاولت ترتيب قائمة المهام أولاً حسب التاريخ ثم حسب الأولوية، فبالتأكيد واجهت موقفاً تبدو فيه النتيجة وكأنها مجموعة عشوائية. لست وحدك — فقد حصد سؤال "Sort collection by multiple fields in Kotlin" آلاف المشاهدات على Stack Overflow. قد يبدو الأمر بسيطاً؟ لكن الشيطان، كما هو الحال دائماً، في التفاصيل.
\nفي هذه المقالة، سنحلل أهم الأخطاء عند الترتيب حسب عدة حقول في Kotlin التي يرتكبها حتى المطورون ذوو الخبرة. ستتعرف على كيفية عمل sortedWith، ولماذا compareBy و thenBy هما أفضل أصدقائك، وكيف لا تفقد البيانات بسبب null. هيا بنا!
الخطأ رقم 1: الترتيب كما في Java — عبر Comparator يدوياً
\nKotlin ليس Java. لكن الكثيرين، القادمين من عالم Java، يكتبون:
\nval list = listOf(\n User("Alice", 25),\n User("Bob", 20),\n User("Charlie", 25)\n)\n\nval sorted = list.sortedWith(Comparator { a, b ->\n val ageCompare = a.age.compareTo(b.age)\n if (ageCompare != 0) ageCompare else a.name.compareTo(b.name)\n})\n\nنعم، هذا يعمل. لكنه غير قابل للقراءة بشكل فظيع، ويمكنك بسهولة أن تتوه في سلاسل if-else. يقدم Kotlin طريقة أنيقة — دوال المقارنة (Comparator functions).
ما الطريقة الصحيحة؟
\nval sorted = list.sortedWith(compareBy<User> { it.age }.thenBy { it.name })\n\nقصيرة، واضحة، بدون منطق زائد. compareBy تنشئ مقارناً أساسياً، و thenBy تضيف الحقول الثانوية. هذا هو الترتيب حسب عدة حقول في Kotlin في أفضل صوره.
الخطأ رقم 2: تجاهل null عند الترتيب
\nلنفترض أن لدينا قائمة مهام حيث deadline قد يكون null. إذا كتبت ببساطة:
tasks.sortedBy { it.deadline }\n\n…ستحصل على NullPointerException. Kotlin صارم تجاه null، وهذا رائع. لكن ماذا لو كان null أمراً طبيعياً؟
\nالحل: nullsFirst / nullsLast
\nاستخدم أغلفة خاصة:
\nval sorted = tasks.sortedWith(\n compareBy<Task>(nullsLast()) { it.deadline }\n .thenBy { it.priority }\n)\n\nهنا، nullsLast() تضع جميع التواريخ null في نهاية القائمة. يوجد أيضاً nullsFirst() — والعكس صحيح. هذا يحل المشكلة، ويصبح ترتيب مجموعات Kotlin آمناً.
الخطأ رقم 3: الخلط بين sortedBy و sortedWith
\nيكتب العديد من المبتدئين:
\nlist.sortedBy { it.age }.sortedBy { it.name }\n\nويتفاجؤون بأن الترتيب حسب الاسم "يلغي" الترتيب حسب العمر. sortedBy تنشئ قائمة جديدة في كل مرة وتُرتب حسب حقل واحد فقط. الاستدعاء الأخير هو المسيطر.
كيف نصلح ذلك؟
\nاستخدم sortedWith مع سلسلة compareBy:
list.sortedWith(compareBy({ it.age }, { it.name }))\n\nأو النسخة متعددة الأسطر للمنطق المعقد:
\nlist.sortedWith(compareBy<Person> { it.age }.thenByDescending { it.name })\n\nتذكر: استدعاء واحد لـ sortedWith — ترتيب واحد حسب عدة حقول.
\n\nالخطأ رقم 4: الترتيب العكسي — في المكان الخطأ
\nأحياناً نحتاج إلى ترتيب تصاعدي للعمر، لكن تنازلي للاسم. الخطأ النموذجي:
\nlist.sortedByDescending { it.age }.sortedBy { it.name }\n\nمرة أخرى نفس المشكلة — الترتيب الثاني يلغي الأول. الطريقة الصحيحة:
\nval sorted = list.sortedWith(\n compareBy<Person> { it.age }\n .thenByDescending { it.name }\n)\n\nأو للتحكم الكامل:
\nval sorted = list.sortedWith(\n compareBy(Person::age).thenByDescending(Person::name)\n)\n<