إطار المجموعات

أدوات Collections المساعدة والمقارنات

15 دقيقة الدرس 10 من 14

أدوات Collections المساعدة والمقارنات

تُعدّ كلاس java.util.Collections مجموعةً من الدوال الثابتة (static) التي تعمل على أنواع List وSet وMap. تتولّى هذه الدوال الترتيب والبحث والخلط وعدّ التكرارات وتغليف المجموعات في طبقات للقراءة فقط أو الحماية بين الخيوط — كل ذلك دون الحاجة إلى كتابة الخوارزمية بنفسك. ستتعلّم في هذا الدرس أيضًا كيف تبني كائنات Comparator قابلة للتسلسل حتى تتمكّن من الترتيب بأي معيار تريد.

الترتيب باستخدام Collections.sort

أبسط استخدام لكلاس الأدوات المساعدة هو ترتيب List تنفّذ عناصره واجهة Comparable. السلاسل النصية والأعداد تنفّذ هذه الواجهة بالفعل، لذا يكفي سطر واحد:

import java.util.ArrayList; import java.util.Collections; import java.util.List; List<String> names = new ArrayList<>(List.of("Charlie", "Alice", "Bob")); Collections.sort(names); System.out.println(names); // [Alice, Bob, Charlie]

تعتمد Collections.sort داخليًا على List.sort، التي تستخدم خوارزمية دمج مستقرة (TimSort). كلمة مستقرة تعني أن العناصر المتساوية تحتفظ بترتيبها الأصلي النسبي — وهذا مهم حين ترتّب بحقل واحد ثم سبق لك الترتيب بحقل آخر.

List.sort مقابل Collections.sort: منذ Java 8 أصبح list.sort(comparator) هو الأسلوب المعتاد — بنفس الكفاءة ويُقرأ بشكل أوضح. أما Collections.sort فهو أقدم وستراه كثيرًا في الكود الذي ستصونه.

العكس والخلط

Collections.reverse تعكس ترتيب القائمة في مكانها. أما Collections.shuffle فتعشوائه، مع إمكانية تمرير Random ذات بذرة محددة لإعادة إنتاج نفس الترتيب:

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5)); Collections.reverse(numbers); System.out.println(numbers); // [5, 4, 3, 2, 1] Collections.shuffle(numbers); System.out.println(numbers); // ترتيب عشوائي، مثلًا: [3, 1, 5, 2, 4]
استخدم shuffle للسحب العادل. إذا أردت اختيار عناصر عشوائيًا دون تكرار — كاختيار أسئلة عشوائية من مجموعة أسئلة — اخلط القائمة وخذ أول N عنصر. هذا أنظف من توليد مؤشرات عشوائية والتحقق من التكرار في كل مرة.

الطبقات غير القابلة للتعديل والمُزامَنة

أحيانًا تريد مشاركة مجموعة مع كود آخر مع منعه من تعديلها. Collections.unmodifiableList (وما يقابلها لـ Set وMap) تغلّف المجموعة الأصلية في طبقة تُلقي UnsupportedOperationException عند أي محاولة كتابة:

List<String> mutable = new ArrayList<>(List.of("read", "only")); List<String> readOnly = Collections.unmodifiableList(mutable); readOnly.get(0); // مسموح readOnly.add("extra"); // يُلقي UnsupportedOperationException
القائمة الأصلية لا تزال قابلة للتعديل. الطبقة غير القابلة للتعديل لا تنسخ البيانات — بل تمنع الكتابة فقط من خلال تلك المرجع. إذا أضفت عنصرًا إلى mutable أعلاه، ستعكسه readOnly. للحصول على لقطة غير قابلة للتعديل حقًا، استخدم List.copyOf(mutable) (Java 10+).

Collections.synchronizedList تغلّف القائمة بحيث يكون كل استدعاء لأي دالة آمنًا للخيوط (thread-safe) على حدة. لمتطلبات التزامن الأكثر تعقيدًا استخدم CopyOnWriteArrayList أو ConcurrentHashMap بدلًا من ذلك — لكن الطبقات المُزامَنة خيار سريع حين تحتاج أمانًا أساسيًا فقط.

دوال أدوات مفيدة أخرى

  • Collections.min(coll) / Collections.max(coll) — إيجاد أصغر أو أكبر عنصر.
  • Collections.frequency(coll, obj) — عدّ كم مرة يظهر obj.
  • Collections.nCopies(n, obj) — إنشاء قائمة غير قابلة للتعديل بـ n نسخة من obj (مفيد للحشو أو القيم الافتراضية).
  • Collections.disjoint(c1, c2) — تُعيد true إذا لم تتشارك المجموعتان أي عنصر.
  • Collections.swap(list, i, j) — تبديل العنصرين عند موضعين.

بناء المقارنات (Comparators)

حين لا تنفّذ العناصر واجهة Comparable، أو تريد ترتيبًا مختلفًا، تمرّر Comparator. الدالة المصنعية Comparator.comparing تنشئ مقارنًا من دالة استخراج مفتاح:

import java.util.Comparator; record Product(String name, double price, int stock) {} List<Product> products = new ArrayList<>(List.of( new Product("Widget", 9.99, 200), new Product("Gadget", 24.99, 50), new Product("Doohickey", 4.99, 500) )); // ترتيب بالسعر تصاعديًا products.sort(Comparator.comparing(Product::price)); products.forEach(p -> System.out.println(p.name() + " $" + p.price())); // Doohickey $4.99 | Widget $9.99 | Gadget $24.99

تسلسل المقارنات

الترتيب الحقيقي غالبًا يحتاج مفتاحًا رئيسيًا ثم مفتاحًا لحل التعادل. توفّر Comparator الدالة thenComparing لهذا الغرض تمامًا:

record Employee(String department, String name, int salary) {} List<Employee> staff = new ArrayList<>(List.of( new Employee("Engineering", "Zara", 90_000), new Employee("Marketing", "Alice", 70_000), new Employee("Engineering", "Alice", 85_000), new Employee("Marketing", "Bob", 70_000) )); Comparator<Employee> byDeptThenName = Comparator.comparing(Employee::department) .thenComparing(Employee::name); staff.sort(byDeptThenName); staff.forEach(e -> System.out.println(e.department() + " | " + e.name())); // Engineering | Alice // Engineering | Zara // Marketing | Alice // Marketing | Bob

عكس المقارنات ومعالجة القيم الفارغة

استدعِ .reversed() على أي مقارن لقلب ترتيبه دون إعادة كتابة دالة استخراج المفتاح:

// الأغلى أولًا products.sort(Comparator.comparing(Product::price).reversed());

إذا كان المفتاح قد يكون null، غلّف المقارن بـ Comparator.nullsFirst أو Comparator.nullsLast:

// ترتيب باسم الأوسط الاختياري؛ القيم الفارغة تذهب للنهاية Comparator<Person> byMiddleName = Comparator.comparing(Person::middleName, Comparator.nullsLast(Comparator.naturalOrder()));
احفظ المقارنات كثوابت مسمّاة. إذا كنت ترتّب نفس القائمة في أماكن متعددة، أخرج Comparator إلى ثابت static final. يجعل ذلك الاختبارات سهلة ويتجنب الأخطاء الدقيقة الناتجة عن بناء المقارن بشكل مختلف في كل مرة.

مثال شامل

إليك مثالًا واقعيًا يجمع بين كلاس الأدوات المساعدة ومقارن مُسلسَل:

List<Product> catalog = new ArrayList<>(List.of( new Product("Widget", 9.99, 200), new Product("Gadget", 24.99, 50), new Product("Gizmo", 24.99, 150), new Product("Doohickey", 4.99, 500) )); // الأرخص أولًا؛ في حالة التعادل الأعلى مخزونًا أولًا Comparator<Product> byPrice = Comparator.comparingDouble(Product::price); Comparator<Product> byStockDesc = Comparator.comparingInt(Product::stock).reversed(); catalog.sort(byPrice.thenComparing(byStockDesc)); catalog.forEach(p -> System.out.println(p.name() + " $" + p.price() + " stock=" + p.stock())); // Doohickey $4.99 stock=500 // Widget $9.99 stock=200 // Gizmo $24.99 stock=150 // Gadget $24.99 stock=50 List<Product> readOnlyCatalog = Collections.unmodifiableList(catalog); System.out.println("Best deal: " + Collections.min(catalog, byPrice).name());

الخلاصة

يمنحك كلاس Collections المساعد خوارزميات جاهزة للإنتاج (ترتيب، عكس، خلط، minimum/maximum، تكرار، طبقات للقراءة فقط والحماية بين الخيوط) باستيراد واحد. وتتيح لك واجهة Comparator التعبير عن أي معيار ترتيب بكود مقروء وقابل للتركيب — بتسلسل thenComparing وreversed والمُغلِّفات الآمنة من القيم الفارغة بدلًا من كتابة كتل if متداخلة. يُكمل هذان الأمران معًا صندوق أدوات إطار Collections الذي بنيته عبر هذا الدرس.