في Java، تعمل حلقة for-each على تبسيط عملية اجتياز أي مجموعة أو مصفوفة، ولكن ليس كل مبرمج Java يعرف بعض تفاصيل حلقة for-each التي سيتم وصفها في هذه المقالة. على عكس المصطلحات الأخرى التي تم إصدارها في Java 5: الأسماء العامة التي تم إصدارها، والتغليف التلقائي والمعلمات المتنوعة، يستخدم مطورو Java حلقات for-each بشكل متكرر أكثر من أي ميزة أخرى، ولكن عندما يُسألون عن كيفية عمل الحلقات المتقدمة for-each، أو ما هي الأساسيات المتطلبات عند استخدام مجموعة في حلقة لكل حلقة، لا يستطيع الجميع الإجابة عليها.
يهدف هذا البرنامج التعليمي والمثال إلى سد هذه الفجوة من خلال الخوض في بعض الألغاز المثيرة للاهتمام في كل حلقة. حسنًا، دعونا لا ندخل في التفاصيل، فلنلقي نظرة على مشكلتنا الأولى في حلقة Java5 for-each.
سؤال الحلقة المتقدمة 1
خذ بعين الاعتبار التعليمة البرمجية التالية التي تجتاز مجمعًا محددًا من قبل المستخدم أو فئة مجموعة، ما الذي سيطبعه هذا الرمز، سواء كان ذلك يسبب استثناءً أو خطأ في المحول البرمجي:
انسخ رمز الكود كما يلي:
اختبار الحزمة
/**
* Java Class لإظهار كيفية عمل كل حلقة في Java
*/
الطبقة العامة ForEachTest {
الفراغ الثابت العام الرئيسي(String args[]){
CustomCollection<String> myCollection = new CustomCollection<String>();
myCollection.add("Java");
myCollection.add("سكالا");
myCollection.add("رائع");
// ماذا سيفعل هذا الرمز، لغة الطباعة، رمي الاستثناء أو خطأ في وقت الترجمة
لـ (لغة السلسلة: myCollection) {
System.out.println(language);
}
}
}
فيما يلي فئة CustomCollection الخاصة بنا، وهي فئة عامة تعتمد، مثل أي فئة مجموعة أخرى، على ArrayList وتوفر طرقًا لإضافة العناصر وإزالتها من المجموعة.
انسخ رمز الكود كما يلي:
اختبار الحزمة
الفئة العامة CustomCollection<T>{
مجموعة ArrayList<T> الخاصة؛
مجموعة مخصصة عامة (){
Bucket = new ArrayList();
}
حجم صحيح العام () {
إرجاع حجم الدلو () ؛
}
المنطقية العامة فارغة () {
إرجاع Bucket.isEmpty();
}
منطقية عامة تحتوي على (T o) {
يحتوي على دلو الإرجاع (س) ؛
}
إضافة منطقية عامة (T e) {
إرجاع Bucket.add(e);
}
إزالة منطقية عامة (T o) {
إرجاع الجرافة.remove(o);
}
}
إجابة:
لن يتم تجميع التعليمات البرمجية أعلاه لأن فئة CustomCollection الخاصة بنا لا تقوم بتنفيذ واجهة java.lang.Iterable. خطأ وقت الترجمة كما يلي:
انسخ رمز الكود كما يلي:
استثناء في مؤشر الترابط "الرئيسي" java.lang.RuntimeException: كود مصدر غير قابل للترجمة - لكل لا ينطبق على نوع التعبير
مطلوب: صفيف أو java.lang.Iterable
تم العثور عليه: test.CustomCollection
في test.ForEachTest.main(ForEachTest.java:24)
هناك حقيقة مثيرة للاهتمام يمكن تعلمها من هذا وهي أن حلقة for-each تنطبق فقط على مصفوفة Java وفئات Collection التي تنفذ واجهة Iterable، وبما أن جميع فئات Collection المضمنة تنفذ واجهة java.util.Collection وقد ورثت Iterable، فإن هذا يمكن رؤية التفاصيل التي غالبًا ما يتم التغاضي عنها في تعريف النوع لواجهة المجموعة "الواجهة العامة Collection Extends Iterable". لذا من أجل حل المشكلة المذكورة أعلاه، يمكنك اختيار السماح لـ CustomCollection ببساطة بتنفيذ واجهة المجموعة أو وراثة AbstractCollection، وهو التنفيذ العالمي الافتراضي ويوضح كيفية استخدام الفئات والواجهات المجردة في نفس الوقت للحصول على مرونة أفضل. الآن دعونا نلقي نظرة على اللغز الثاني من حلقة for-each:
الصعوبة الثانية في حلقات Java for-each:
سوف يطرح مثال التعليمات البرمجية التالي ConcurrentModificationException. نستخدم هنا مكررًا قياسيًا وحلقة لكل حلقة للتكرار عبر ArrayList ثم حذف العناصر. تحتاج إلى معرفة أي جزء من التعليمات البرمجية سيطرح ConcurrentModificationException ولماذا؟ لاحظ أن الإجابة يمكن أن تكون كلاهما، أو لا، أو أحدهما أو الآخر.
انسخ رمز الكود كما يلي:
اختبار الحزمة
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* فئة Java لتوضيح العمل الداخلي لكل حلقة في Java
* @ المؤلف جافين بول
**/
الطبقة العامة ForEachTest2 {
الفراغ الثابت العام الرئيسي(String args[]){
Collection<String> list = new ArrayList<String>();
list.add("أندرويد");
list.add("ايفون");
list.add("ويندوز موبايل");
// ما هو الكود الذي سيطرح ConcurrentModificationException، كلاهما،
// لا شيء أو واحد منهم
// مثال 1
Itr<String> itr = list.iterator();
بينما(itr.hasNext()){
String lang = itr.next();
list.remove(lang);
}
//مثال 2
لـ (لغة السلسلة: القائمة) {
list.remove(language);
}
}
}
سيقول حوالي 70% من مطوري Java أن أول كتلة تعليمات برمجية ستطرح استثناء ConcurrentModificationException لأننا لا نستخدم طريقة الإزالة الخاصة بالمكرر لحذف العناصر، ولكننا نستخدم طريقة الإزالة () الخاصة بـ ArrayList. ومع ذلك، لا يقول العديد من مطوري Java أن نفس المشكلة تحدث مع كل حلقة لأننا لا نستخدم المكرر هنا. في الواقع، يعرض مقتطف الكود الثاني أيضًا ConcurrentModificationException، والذي يصبح واضحًا بعد حل الالتباس الأول. نظرًا لأن حلقة for-each تستخدم Iterator داخليًا لاجتياز المجموعة، فإنها تستدعي أيضًا Iterator.next()، الذي يتحقق من التغييرات (للعناصر) ويطرح ConcurrentModificationException. يمكنك رؤية ذلك من الناتج أدناه، والذي تحصل عليه عند تشغيل المقتطف الثاني بعد التعليق على المقتطف الأول.
انسخ رمز الكود كما يلي:
استثناء في مؤشر الترابط "الرئيسي" java.util.ConcurrentModificationException
في java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
في java.util.AbstractList$Itr.next(AbstractList.java:343)
في test.ForEachTest2.main(ForEachTest2.java:34)
هذا كل ما يتعلق بحلقة Java5 لكل حلقة. لقد رأينا الكثير من المشاكل التي يواجهها مبرمجو Java عند كتابة التعليمات البرمجية للتكرار عبر فئة Collection، خاصة عند التكرار عبر مجموعة أثناء حذف العناصر في نفس الوقت. تذكر دائمًا استخدام طريقة الإزالة الخاصة بـ Iterator عند إزالة الكائنات من أي مجموعة (مثل الخريطة أو المجموعة أو القائمة). تذكر أيضًا أن حلقة for-each هي مجرد سكر نحوي (سكر نحوي) بالإضافة إلى الاستخدام القياسي لـ. رمز التكرار القياسي للسكر) فقط.
ملاحظة المترجم: السكر النحوي، والذي يُترجم أيضًا إلى النحو المغطى بالسكر، هو مصطلح ابتكره عالم الكمبيوتر البريطاني بيتر ج. لاندين، والذي يشير إلى نوع معين من القواعد النحوية المضافة إلى لغات الكمبيوتر. ليس للتركيب النحوي أي تأثير على وظيفة اللغة، ولكنها تسهل على المبرمجين استخدامها. بشكل عام، استخدام السكر في بناء الجملة يمكن أن يزيد من سهولة قراءة البرنامج، وبالتالي يقلل من فرصة حدوث أخطاء في كود البرنامج.