واجهة Comparator
جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 45 من 87.
بينما يحدد Comparable الترتيب الطبيعي للفئة، قد تحتاج أحياناً إلى فرز الكائنات بطرق مختلفة. تتيح لك واجهة Comparator إنشاء منطق مقارنة منفصل دون تعديل الفئة الأصلية.
يُعد Comparator كائناً خارجياً يقارن بين كائنين من نفس النوع. هذا مفيد عندما تريد خيارات فرز متعددة أو عندما لا يمكنك تعديل الفئة (class) نفسها:
import java.util.Comparator;
class NameComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p1.getName().compareTo(p2.getName());
}
}
class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}يمكنك بعد ذلك تمرير المقارن (comparator) إلى دوال الفرز:
List<Person> people = new ArrayList<>();
people.add(new Person("Charlie", 30));
people.add(new Person("Alice", 25));
Collections.sort(people, new NameComparator()); // مرتب حسب الاسم
Collections.sort(people, new AgeComparator()); // مرتب حسب العمرللحصول على كود مختصر، يمكنك استخدام تعبيرات لامدا (lambda expressions) بما أن Comparator هي واجهة وظيفية (functional interface):
Collections.sort(people, (p1, p2) -> p1.getName().compareTo(p2.getName()));الفرق الجوهري: يتم تنفيذ Comparable بواسطة الفئة التي تتم مقارنتها ويحدد ترتيباً طبيعياً واحداً، بينما Comparator هو فئة منفصلة يمكنها تحديد ترتيبات مخصصة غير محدودة.
التحدي
سهللنقم ببناء نظام لفرز الموظفين يوضح قوة واجهة Comparator. ستقوم بإنشاء مقارنات متعددة لفرز الموظفين بطرق مختلفة — حسب الراتب وحسب الاسم — دون تعديل فئة Employee نفسها.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
Employee.java: قم بإنشاء فئة تمثل موظفاً بثلاثة حقول خاصة:name(String)، وdepartment(String)، وsalary(double). قم بتضمين منشئ (constructor) لتهيئة جميع الحقول وطرق جلب (getter methods) لكل منها. قم بإعادة كتابةtoString()لتعيد:[name] ([department]) - $[salary]SalaryComparator.java: قم بإنشاء مقارن يقوم بفرز الموظفين حسب الراتب بترتيب تنازلي (الأعلى أجراً أولاً). قم بتنفيذ واجهةComparator<Employee>وطريقةcompare()الخاصة بها. استخدمDouble.compare()للمقارنة الآمنة لقيم الرواتب.NameComparator.java: قم بإنشاء مقارن آخر يقوم بفرز الموظفين أبجدياً حسب الاسم بترتيب تصاعدي (من A إلى Z). قم بتنفيذComparator<Employee>واستخدم طريقةcompareTo()الخاصة بـ String للمقارنة.Main.java: اجمع كل شيء معاً عن طريق إنشاء قائمة من الموظفين وفرزهم باستخدام المقارنات الخاصة بك. ستتلقى مدخلات لثلاثة موظفين، كل منهم في سطر منفصل بالتنسيق:name,department,salaryقم بإنشاء
ArrayListمن الموظفين، ثم استعرض كلا نهجي الفرز:- أولاً، قم بالفرز حسب الراتب باستخدام
SalaryComparatorالخاص بك واطبع كل موظف - اطبع سطراً فارغاً
- ثم قم بالفرز حسب الاسم باستخدام
NameComparatorالخاص بك واطبع كل موظف
- أولاً، قم بالفرز حسب الراتب باستخدام
ستتلقى ثلاثة أسطر من المدخلات، يحتوي كل منها على بيانات الموظف بالتنسيق: name,department,salary
على سبيل المثال، قد يبدو سطر الإدخال كما يلي: Alice,Engineering,75000.0
تذكر استيراد java.util.Comparator في ملفات المقارن الخاصة بك، و java.util.ArrayList و java.util.Collections و java.util.Scanner في ملف Main الخاص بك. لاحظ كيف تسمح لك المقارنات بتعريف استراتيجيات فرز متعددة خارجياً — لا تحتاج فئة Employee إلى معرفة أي شيء عن كيفية فرزها!
ورقة مرجعية
تتيح لك واجهة Comparator إنشاء منطق مقارنة خارجي لفرز الكائنات دون تعديل الفئة الأصلية. هذا مفيد عندما تحتاج إلى خيارات فرز متعددة.
تقوم واجهة Comparator بمقارنة كائنين من نفس النوع من خلال تنفيذ طريقة compare():
import java.util.Comparator;
class NameComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p1.getName().compareTo(p2.getName());
}
}قم بتمرير الـ comparator إلى طرق الفرز:
Collections.sort(people, new NameComparator());يمكنك استخدام تعبيرات لامبدا (lambda expressions) للحصول على كود موجز لأن Comparator هي واجهة وظيفية (functional interface):
Collections.sort(people, (p1, p2) -> p1.getName().compareTo(p2.getName()));لمقارنة القيم الرقمية، استخدم طرق فئات الغلاف (wrapper class) مثل Double.compare() لإجراء مقارنة آمنة:
class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}الفرق الرئيسي: تحدد Comparable ترتيباً طبيعياً واحداً داخل الفئة نفسها، بينما Comparator هي فئة منفصلة يمكنها تحديد ترتيبات مخصصة غير محدودة خارجياً.
جرّب بنفسك
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// قراءة ثلاثة موظفين من المدخلات
// تنسيق كل سطر: الاسم، القسم، الراتب
String line1 = scanner.nextLine();
String line2 = scanner.nextLine();
String line3 = scanner.nextLine();
// TODO: قم بتحليل كل سطر وإنشاء كائنات موظف (Employee)
// تلميح: استخدم split(",") لفصل القيم
// TODO: قم بإنشاء ArrayList للموظفين وأضف الثلاثة جميعاً
// TODO: قم بالفرز حسب الراتب باستخدام SalaryComparator واطبع كل موظف
// تلميح: استخدم Collections.sort(list, comparator)
// TODO: اطبع سطراً فارغاً
// TODO: قم بالفرز حسب الاسم باستخدام NameComparator واطبع كل موظف
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر