break و continue في forEach في Kotlin: كيف يعمل ذلك تحت الغطاء

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

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

ابدأ الدورة

لماذا لا تعمل break و continue في forEach في Kotlin (وكيفية إصلاح ذلك)

\n\n

إذا كنت قد كتبت أي وقت مضى بلغة Kotlin وحاولت الخروج مبكرًا من حلقة forEach باستخدام break، فمن المحتمل أنك واجهت خطًا أحمر تحته في بيئة التطوير المتكاملة (IDE) وخطأ في الترجمة. هل هذا مألوف؟ أنت لست وحدك - هذا أحد أكثر الأسئلة شيوعًا على Stack Overflow حول Kotlin، وقد حصد آلاف المشاهدات. في هذه المقالة، لن نعرض فقط كيفية تجاوز هذا القيد، بل سننظر أيضًا تحت الغطاء: لماذا تم تصميمه بهذه الطريقة، وكيف يعمل return داخل اللامبدا، وما هي البدائل الموجودة. هيا بنا!

\n\n

مثال كلاسيكي: خطأ المبتدئ

\n\n

تخيل أن لديك قائمة من الأرقام، وتحتاج إلى طباعة أول ثلاثة عناصر فقط على الشاشة. بشكل بديهي، قد ترغب في كتابة ما يلي:

\n\n
val numbers = listOf(1, 2, 3, 4, 5)\nnumbers.forEach {\n    println(it)\n    if (it == 3) break // خطأ في الترجمة!\n}\n
\n\n

سيصدر المترجم خطأ: ‘break’ and ‘continue’ are only allowed inside a loop. لماذا؟ أليس forEach عبارة عن حلقة؟ الإجابة تكمن في كيفية عمل forEach في الواقع.

\n\n

تشريح forEach: إنها ليست حلقة، بل استدعاء دالة

\n\n

دعنا نفتح المكتبة القياسية لـ Kotlin وننظر إلى توقيع forEach:

\n\n
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {\n    for (element in this) action(element)\n}\n
\n\n

الكلمة المفتاحية هنا هي inline. هذا يعني أنه في مرحلة الترجمة، يتم إدراج جسم الدالة مباشرة في مكان الاستدعاء. ولكن مع ذلك، forEach هي دالة ذات ترتيب أعلى تقبل دالة أخرى (لامبدا) كوسيطة. واللامبدا ليست كتلة كود حلقة، بل هي كائن دالة عادي. break و continue لهما معنى فقط داخل تركيبات for أو while أو do-while، وليس داخل دالة عشوائية.

\n\n

بعبارة أخرى، عندما تكتب numbers.forEach { ... }، فإنك لا تنشئ حلقة - بل تستدعي طريقة تستخدم داخليًا حلقة for. لا يمكن للمترجم "تمرير" break من اللامبدا الخاصة بك إلى الحلقة الخارجية، لأن اللامبدا هي كيان منفصل. هذا ليس خطأ، بل هو تصميم مقصود.

\n\n

كيف يعمل return في forEach (منعطف غير متوقع)

\n\n

أما return داخل forEach فهو يعمل... ولكن ليس كما تتوقع. انظر:

\n\n
fun main() {\n    val numbers = listOf(1, 2, 3, 4, 5)\n    numbers.forEach {\n        if (it == 3) return // خروج من main()!\n        println(it)\n    }\n    println("هذا السطر لن يتم تنفيذه")\n}\n
\n\n

سيكون الإخراج: 1 2. return هنا هو non-local return (إرجاع غير محلي). إنه لا ينهي اللامبدا فحسب، بل ينهي أيضًا الدالة الخارجية بأكملها (main). يعمل هذا لأن forEach تم تعريفه على أنه inline. يقوم المترجم بتضمين كود اللامبدا مباشرة في جسم main، ويصبح return يشير إلى أقرب fun معلنة، أي main.

\n\n

إذا كنت تريد الخروج فقط من forEach وليس من الدالة بأكملها، استخدم وسمًا (label):

\n\n
fun main() {\n    val numbers = listOf(1, 2, 3, 4, 5)\n    numbers.forEach loop@{\n        if (it == 3) return@loop\n        println(it)\n    }\n    println("هذا السطر سيتم تنفيذه")\n}\n
\n\n

الإخراج: 1 2 و هذا السطر سيتم تنفيذه. الوسم loop@ يخبر المترجم: "اخرج من اللامبدا، ولكن ليس من main". في جوهره، هذا مشابه لـ continue - نحن نتخطى التكرار الحالي وننتقل إلى العنصر التالي.

\n\n

كيفية محاكاة break و continue في forEach<

المدونات

توصيات الكتب