المُكرِّرات وواجهة Iterable
المُكرِّرات وواجهة Iterable
في كل مرة تكتب فيها حلقة for-each على List أو Set، يعمل شيء ما بصمت في الخلفية: بروتوكول Iterator. فهمه يُمكّنك من اجتياز المجموعات بأمان، وحذف العناصر في أثناء الحلقة دون أخطاء، بل وبناء هياكل بيانات قابلة للتكرار بنفسك.
واجهة Iterator
تُعرِّف واجهة java.util.Iterator<E> ثلاث توابع:
boolean hasNext()— تُعيدtrueإذا كانت ثمّة عناصر لم تُزَر بعد.E next()— تُعيد العنصر التالي وتُحرّك المؤشر للأمام.void remove()— تحذف العنصر الذي أعادته آخر استدعاء لـnext()من المجموعة الأصلية (عملية اختيارية).
تحصل على مُكرِّر من أي مجموعة عبر تابعها iterator():
هذا بالضبط ما تُترجَم إليه حلقة for-each. يُعيد المُصرِّف كتابة for (String c : cities) إلى صيغة while (it.hasNext()) الموضحة أعلاه.
الحذف الآمن للعناصر أثناء التكرار
خطأ شائع للمبتدئين هو استدعاء list.remove() داخل حلقة for-each. يُسبّب هذا رمي استثناء ConcurrentModificationException لأن المجموعة تكتشف أنّها عُدِّلت هيكليًا بينما مُكرِّر نشط.
ConcurrentModificationException.
الأسلوب الصحيح هو استخدام Iterator.remove() بدلًا من ذلك:
القاعدة بسيطة: استدعِ next() دائمًا قبل remove(). استدعاء remove() مرتين متتاليتين دون next() بينهما يُطلق IllegalStateException.
collection.removeIf(predicate) أكثر وضوحًا للحذف الجماعي ويتعامل مع المُكرِّر داخليًا. استخدمه حين لا تحتاج إلى منطق يتجاوز شرطًا بسيطًا: cities.removeIf(c -> c.startsWith("D"));
واجهة Iterable
تمتلك واجهة java.lang.Iterable<T> تابعًا واحدًا مطلوبًا فقط:
أي صنف يُنفّذ Iterable يمكن استخدامه في حلقة for-each. جميع واجهات المجموعات القياسية (Collection، List، Set، Queue) تمتدّ من Iterable، وهذا هو سبب دعمها كلّها لحلقة for-each.
تنفيذ Iterable على صنف مخصص
افترض أن لديك نوع نطاق بسيط يُمثّل تسلسلًا من الأعداد الصحيحة من start إلى end (غير شاملة). بتنفيذ Iterable<Integer> تحصل على دعم for-each مجانًا:
الاستخدام صياغة Java اصطلاحية نظيفة:
iterator() مُكرِّرًا جديدًا مستقلًا. إذا أعدت الكائن ذاته مرتين، تبدأ حلقة for-each الثانية من منتصف التسلسل أو تجده مُستنفَدًا. الصنف الداخلي المجهول أعلاه يلتقط current كمتغيّر محلّي، فكل استدعاء لـ iterator() يُنشئ نسخة جديدة تبدأ من current = start.
ListIterator — الاجتياز ثنائي الاتجاه
تمتدّ java.util.ListIterator<E> من Iterator وتُضيف الاجتياز الخلفي والاستبدال في الموضع ذاته، وهي متاحة على أي List:
يمكنك أيضًا الاجتياز للخلف عبر hasPrevious() وprevious()، أو إدراج عناصر بـ add().
متى تستخدم Iterator بشكل صريح
في الكود اليومي، فضّل حلقة for-each أو تدفقات Stream. الجأ إلى مُكرِّر صريح فقط حين تحتاج إلى:
- حذف عناصر أثناء الاجتياز (استخدم
Iterator.remove()أوremoveIf()). - استبدال عناصر في
Listأثناء الاجتياز (استخدمListIterator.set()). - التناوب بين مُكرِّرَين من المجموعة ذاتها في حلقة واحدة.
- تنفيذ نوع
Iterableمخصص.
الخلاصة
تُوفّر Iterator<E> بروتوكول اجتياز موحّدًا: hasNext()، وnext()، والـ remove() الآمن. أما واجهة Iterable<T> — التي تابعها الوحيد يُعيد Iterator — فهي ما يُفتح حلقة for-each لأي نوع. حين تحتاج إلى حذف عناصر أثناء التكرار، استخدم دائمًا remove() الخاص بالمُكرِّر لا تابع المجموعة. وحين تُصمّم حاوية مخصصة، يُعدّ تنفيذ Iterable الطريقة الأنيقة لمنح العملاء اجتيازًا اصطلاحيًا.