تنفيذ واجهة المقارنة
يمكن رؤية واجهة المقارنة في كل مكان في مكتبة JDK، من البحث إلى الفرز إلى العمليات العكسية، وما إلى ذلك. في Java 8، تصبح واجهة وظيفية وتتمثل ميزة ذلك في أنه يمكننا استخدام بناء الجملة المتدفق لتنفيذ المقارنة.
دعونا ننفذ Comparator بعدة طرق مختلفة لمعرفة قيمة بناء الجملة الجديد. سوف تشكرك أصابعك على عدم الاضطرار إلى تنفيذ فئات داخلية مجهولة مما يوفر عليك الكثير من ضغطات المفاتيح.
الفرز باستخدام المقارنة
سيستخدم المثال التالي طرق مقارنة مختلفة لفرز مجموعة من الأشخاص. لنقم أولاً بإنشاء شخص JavaBean.
انسخ رمز الكود كما يلي:
شخص من الدرجة العامة {
اسم السلسلة النهائية الخاصة؛
العمر النهائي الخاص؛
شخص عام (السلسلة النهائية theName، Final int theAge) {
الاسم = الاسم؛
العمر = العمر؛
}
سلسلة عامة getName() { اسم الإرجاع }
public int getAge() { إرجاع العمر }
public int ageDifference(الشخص النهائي آخر) {
عمر العودة -other.age؛
}
سلسلة عامة إلى سلسلة () {
return String.format("%s - %d"، الاسم، العمر)؛
}
}
يمكننا تنفيذ واجهة المقارنة من خلال فئة الشخص، ولكن بهذه الطريقة يمكننا استخدام طريقة مقارنة واحدة فقط. نريد أن نكون قادرين على مقارنة السمات المختلفة - مثل الاسم أو العمر أو مزيج من هذه السمات. من أجل إجراء المقارنة بمرونة، يمكننا استخدام Comparator لإنشاء التعليمات البرمجية ذات الصلة عندما نحتاج إلى المقارنة.
لنقم أولاً بإنشاء قائمة بالأشخاص، ولكل منهم اسم وعمر مختلف.
انسخ رمز الكود كما يلي:
القائمة النهائية<Person> الأشخاص = Arrays.asList(
شخص جديد ("جون"، 20)،
شخص جديد ("سارة"، 21)،
شخص جديد ("جين"، 21)،
شخص جديد ("جريج"، 35))؛
يمكننا فرز الأشخاص بترتيب تصاعدي أو تنازلي حسب أسمائهم أو أعمارهم. الطريقة العامة هي استخدام الفئات الداخلية المجهولة لتنفيذ واجهة المقارنة. إذا تمت كتابته بهذه الطريقة، فإن الكود الأكثر صلة فقط هو الذي يكون ذا معنى، والباقي مجرد إجراء شكلي. يمكن أن يؤدي استخدام تعبيرات لامدا إلى التركيز على جوهر المقارنة.
دعونا أولا نفرزهم حسب العمر من الأصغر إلى الأكبر.
الآن بعد أن أصبح لدينا كائن قائمة، يمكننا استخدام طريقة الفرز () الخاصة به للفرز. ومع ذلك، فإن هذه الطريقة لها أيضًا مشاكلها. هذه طريقة باطلة، مما يعني أنه عندما نستدعي هذه الطريقة، ستتغير القائمة. للاحتفاظ بالقائمة الأصلية، يجب علينا أولاً عمل نسخة ثم استدعاء طريقة الفرز (). لقد كان مجرد الكثير من الجهد. في هذا الوقت علينا أن نلجأ إلى فئة البث للحصول على المساعدة.
يمكننا الحصول على كائن دفق من القائمة ثم استدعاء الطريقة المصنفة () الخاصة به. تقوم بإرجاع مجموعة مفروزة بدلاً من تعديل المجموعة الأصلية. باستخدام هذه الطريقة، يمكنك بسهولة تكوين معلمات المقارنة.
انسخ رمز الكود كما يلي:
قائمة<شخص> تصاعدي =
People.stream()
.sorted((person1, person2) -> person1.ageDifference(person2))
.collect(toList());
printPeople("مرتبة تصاعديًا حسب العمر:"، ascendingAge);
نقوم أولاً بتحويل القائمة إلى كائن دفق من خلال طريقة الدفق (). ثم اتصل بالطريقة المصنفة (). تقبل هذه الطريقة معلمة المقارنة. نظرًا لأن Comparator عبارة عن واجهة وظيفية، فيمكننا تمرير تعبير lambda. أخيرًا نطلق على طريقة التجميع ونطلب منها تخزين النتائج في قائمة. طريقة التجميع عبارة عن مُخفض يمكنه إخراج الكائنات أثناء عملية التكرار إلى تنسيق أو نوع محدد. طريقة toList() هي طريقة ثابتة لفئة Collectors.
تتلقى الطريقة المجردة CompareTo() الخاصة بـ Comparator معلمتين، وهما الكائنات المراد مقارنتها، وتقوم بإرجاع نتيجة من النوع int. لكي يكون متوافقًا مع هذا، يتلقى تعبير lambda الخاص بنا أيضًا معلمتين، كائنين Person، يتم استنتاج أنواعهما تلقائيًا بواسطة المترجم. نعيد نوع int للإشارة إلى ما إذا كانت الكائنات المقارنة متساوية.
ولأننا نريد الفرز حسب العمر، فسنقارن عمر الكائنين ثم نعيد نتيجة المقارنة. إذا كانت بنفس الحجم، قم بإرجاع 0. بخلاف ذلك، يتم إرجاع رقم سالب إذا كان الشخص الأول أصغر سنًا، ويتم إرجاع رقم موجب إذا كان الشخص الأول أكبر سنًا.
سوف تقوم الطريقة التي تم فرزها () باجتياز كل عنصر من عناصر المجموعة المستهدفة واستدعاء المقارنة المحددة لتحديد ترتيب فرز العناصر. طريقة تنفيذ الطريقة التي تم فرزها () تشبه إلى حد ما طريقة التخفيض () المذكورة سابقًا. تعمل طريقة التخفيض () على تقليل القائمة تدريجيًا إلى نتيجة. يتم فرز طريقة الترتيب () حسب نتائج المقارنة.
بمجرد الانتهاء من الفرز، نريد طباعة النتائج، لذلك نستدعي طريقة printPeople () لننفذ هذه الطريقة.
انسخ رمز الكود كما يلي:
طباعة باطلة عامة ثابتة (
رسالة السلسلة النهائية، القائمة النهائية <Person> الأشخاص) {
System.out.println(message);
People.forEach(System.out::println);
}
في هذه الطريقة، نقوم أولاً بطباعة رسالة، ثم نجتاز القائمة ونطبع كل عنصر فيها.
لنستدعي الطريقةsorted() لنرى كيف سيتم فرز الأشخاص في القائمة من الأصغر إلى الأقدم.
انسخ رمز الكود كما يلي:
الترتيب تصاعدياً حسب العمر:
جون - 20
سارة - 21
جين - 21
جريج - 35
دعونا نلقي نظرة أخرى على الطريقة التي تم فرزها () لإجراء تحسينات.
انسخ رمز الكود كما يلي:
.sorted((person1, person2) -> person1.ageDifference(person2))
في تعبير lambda الذي تم تمريره، نقوم ببساطة بتوجيه هاتين المعلمتين - يتم استخدام المعلمة الأولى كهدف استدعاء لأسلوب ageDifference()، ويتم استخدام المعلمة الثانية كمعلمة خاصة بها. لكن لا يمكننا كتابتها بهذه الطريقة، ولكننا نستخدم وضع مساحة المكتب، أي نستخدم مراجع الطريقة ونترك مترجم Java يقوم بالتوجيه.
يختلف توجيه المعلمة المستخدم هنا قليلاً عما رأيناه من قبل. لقد رأينا سابقًا أنه يتم تمرير الوسائط إما كأهداف استدعاء أو كمعلمات استدعاء. الآن، لدينا معلمتان، ونريد تقسيمهما إلى قسمين، أحدهما كهدف لاستدعاء الطريقة، والثاني كمعلمة. لا تقلق، سيخبرك مترجم Java، "سأعتني بهذا".
يمكننا استبدال تعبير لامدا في طريقة الترتيب () السابقة بطريقة AgeDifference قصيرة وموجزة.
انسخ رمز الكود كما يلي:
People.stream()
.sorted(شخص::ageDifference)
هذا الكود موجز للغاية، وذلك بفضل مراجع الطريقة التي يوفرها مترجم Java. يتلقى المترجم معلمتين لمثيلات الشخص ويستخدم الأول كهدف لطريقة ageDifference() والثاني كمعلمة للطريقة. نسمح للمترجم بالقيام بهذا العمل بدلاً من كتابة الكود مباشرةً. عند استخدام هذه الطريقة، يجب علينا التأكد من أن المعلمة الأولى هي هدف الاتصال للطريقة المشار إليها، والمعلمة المتبقية هي معلمة الإدخال للطريقة.
إعادة استخدام المقارنة
من السهل فرز الأشخاص الموجودين في القائمة من الأصغر إلى الأكبر، ومن السهل أيضًا الفرز من الأكبر إلى الأصغر. دعونا نجربها.
انسخ رمز الكود كما يلي:
printPeople("مرتبة تنازليًا حسب العمر:"،
People.stream()
.sorted((person1, person2) -> person2.ageDifference(person1))
.collect(toList()));
نحن نستدعي الطريقةsorted() ونمرر تعبير lambda الذي يتناسب مع واجهة المقارنة، تمامًا كما في المثال السابق. والفرق الوحيد هو تنفيذ تعبير لامدا هذا - لقد قمنا بتغيير ترتيب الأشخاص المراد مقارنتهم. وينبغي ترتيب النتائج من الأكبر إلى الأصغر من حيث أعمارهم. دعونا نلقي نظرة.
انسخ رمز الكود كما يلي:
الترتيب تنازلياً حسب العمر:
جريج - 35
سارة - 21
جين - 21
جون - 20
لا يتطلب الأمر الكثير من الجهد لتغيير منطق المقارنة. لكن لا يمكننا إعادة بناء هذا الإصدار إلى مرجع الطريقة لأن ترتيب المعلمات لا يتوافق مع قواعد توجيه المعلمة لمراجع الطريقة؛ لا يتم استخدام المعلمة الأولى كهدف استدعاء للطريقة، ولكن كمعلمة للأسلوب. هناك طريقة لحل هذه المشكلة تقلل أيضًا من ازدواجية الجهود. دعونا نرى كيفية القيام بذلك.
لقد أنشأنا تعبيرين لامدا من قبل: أحدهما للفرز حسب العمر من الصغير إلى الكبير، والآخر للفرز من الأكبر إلى الأصغر. سيؤدي القيام بذلك إلى تكرار التعليمات البرمجية وتكرارها، وانتهاك مبدأ DRY. إذا أردنا فقط ضبط ترتيب الفرز، فإن JDK يوفر طريقة عكسية تحتوي على مُعدِّل طريقة خاص، وهو الإعداد الافتراضي. سنناقش ذلك بالطريقة الافتراضية في الصفحة 77. هنا نستخدم أولاً الطريقة العكسية () لإزالة التكرار.
انسخ رمز الكود كما يلي:
المقارنة<الشخص> المقارنة التصاعدية =
(الشخص 1، الشخص 2) -> الشخص 1.ageDifference(person2);
Comparator<Person> CompareDescending = CompareAscending.reversed();
لقد أنشأنا أولاً أداة مقارنة، تُدعى CompareAscending، لفرز الأشخاص حسب العمر من الأصغر إلى الأكبر. من أجل عكس ترتيب المقارنة، بدلاً من كتابة هذا الرمز مرة أخرى، نحتاج فقط إلى استدعاء الطريقة العكسية () للمقارنة الأولى للحصول على كائن المقارنة الثاني. تحت غطاء الطريقة المعكوسة()، تقوم بإنشاء مقارن لعكس ترتيب المعلمات المقارنة. يوضح هذا أن العكس هو أيضًا أسلوب ذو ترتيب أعلى - فهو ينشئ ويعيد دالة بدون آثار جانبية. نستخدم هذين المقارنين في الكود.
انسخ رمز الكود كما يلي:
printPeople("مرتبة تصاعديًا حسب العمر:"،
People.stream()
.مرتبة(مقارنةتصاعدية)
.جمع (إلى القائمة ())
);
printPeople("مرتبة تنازليًا حسب العمر:"،
People.stream()
.مرتبة (مقارنة تنازلية)
.جمع (إلى القائمة ())
);
يمكن أن نرى بوضوح من التعليمات البرمجية أن هذه الميزات الجديدة لـ Java8 قد قللت بشكل كبير من تكرار وتعقيد التعليمات البرمجية، ولكن الفوائد أكثر بكثير من هذه. هناك إمكانيات لا حصر لها في انتظارك لاستكشافها في JDK.
يمكننا بالفعل الفرز حسب العمر، ومن السهل أيضًا الفرز حسب الاسم. دعونا نصنفها معجميًا حسب الاسم، وبالمثل، نحتاج فقط إلى تغيير المنطق في تعبير لامدا.
انسخ رمز الكود كما يلي:
printPeople("تم الترتيب تصاعديًا حسب الاسم:"،
People.stream()
.sorted ((الشخص 1، الشخص 2) ->
person1.getName().compareTo(person2.getName()))
.collect(toList()));
سيتم فرز نتائج الإخراج بالترتيب المعجمي حسب الاسم.
انسخ رمز الكود كما يلي:
الترتيب تصاعدياً حسب الاسم:
جريج - 35
جين - 21
جون - 20
سارة - 21
حتى الآن، قمنا بالفرز إما حسب العمر أو حسب الاسم. يمكننا أن نجعل منطق تعبيرات لامدا أكثر ذكاءً. على سبيل المثال، يمكننا الفرز حسب العمر والاسم في نفس الوقت.
دعونا نختار أصغر شخص في القائمة. يمكننا أولاً الفرز حسب العمر من الأصغر إلى الأكبر ثم تحديد الأول في النتائج. لكن هذا لا يعمل في الواقع. لدى Stream طريقة min() لتحقيق ذلك. تقبل هذه الطريقة أيضًا المقارنة، ولكنها تُرجع أصغر كائن في المجموعة. دعونا استخدامه.
انسخ رمز الكود كما يلي:
People.stream()
.min(شخص::ageDifference)
.ifPresent(youngest -> System.out.println("الأصغر: " + الأصغر));
عند استدعاء الأسلوب min()، استخدمنا مرجع الأسلوب ageDifference. تقوم الطريقة min() بإرجاع كائن Optinal لأن القائمة قد تكون فارغة وقد يكون هناك أكثر من شخص أصغر سناً فيها. ثم نحصل على أصغر شخص من خلال طريقة ifPrsend() الخاصة بـ Optinal ونطبع معلوماته التفصيلية. دعونا نلقي نظرة على الإخراج.
انسخ رمز الكود كما يلي:
الأصغر: جون - 20
يعد تصدير الأقدم أيضًا أمرًا بسيطًا للغاية. ما عليك سوى تمرير مرجع الطريقة هذا إلى طريقة max().
انسخ رمز الكود كما يلي:
People.stream()
.ماكس (الشخص::الفرق العمري)
.ifPresent(eldest -> System.out.println("الأكبر: " + الأكبر));
دعونا نلقي نظرة على اسم وعمر الأكبر.
انسخ رمز الكود كما يلي:
الاكبر: جريج - 35
باستخدام تعبيرات لامدا ومراجع الطريقة، يصبح تنفيذ المقارنات أسهل وأكثر ملاءمة. تقدم JDK أيضًا العديد من الطرق الملائمة لفئة Compararor، مما يسمح لنا بإجراء مقارنة أكثر سلاسة، كما سنرى أدناه.
مقارنات متعددة ومقارنات التدفق
دعونا نلقي نظرة على الطرق الجديدة الملائمة التي توفرها واجهة المقارنة ونستخدمها لمقارنة خصائص متعددة.
دعنا نواصل استخدام المثال من القسم السابق. الترتيب حسب الاسم، وهذا ما كتبناه أعلاه:
انسخ رمز الكود كما يلي:
People.stream()
.sorted ((الشخص 1، الشخص 2) ->
person1.getName().compareTo(person2.getName()));
بالمقارنة مع طريقة الكتابة للطبقة الداخلية في القرن الماضي، فإن طريقة الكتابة هذه بسيطة للغاية. ومع ذلك، يمكن أن يكون الأمر أسهل إذا استخدمنا بعض الوظائف في فئة المقارنة. استخدام هذه الوظائف يمكن أن يسمح لنا بالتعبير عن هدفنا بشكل أكثر سلاسة. على سبيل المثال، إذا أردنا الفرز حسب الاسم، يمكننا أن نكتب:
انسخ رمز الكود كما يلي:
الوظيفة النهائية <Person, String> byName = person -> person.getName();
People.stream()
.sorted(comparing(byName));
في هذا الكود قمنا باستيراد الطريقة الثابتة لمقارنة () لفئة المقارنة. تستخدم طريقة المقارنة () تعبير لامدا الذي تم تمريره لإنشاء كائن مقارنة. بمعنى آخر، إنها أيضًا دالة ذات ترتيب أعلى تقبل دالة كمعلمة إدخال وترجع دالة أخرى. بالإضافة إلى جعل بناء الجملة أكثر إيجازًا، يمكن لمثل هذه التعليمات البرمجية أيضًا أن تعبر بشكل أفضل عن المشكلة الفعلية التي نريد حلها.
مع ذلك، يمكن أن تصبح المقارنات المتعددة أكثر سلاسة. على سبيل المثال، الكود التالي للمقارنة حسب الاسم والعمر يوضح كل شيء:
انسخ رمز الكود كما يلي:
الوظيفة النهائية<Person, Integer> byAge = person -> person.getAge();
الوظيفة النهائية<Person, String> byTheirName = person -> person.getName();
printPeople("مرتبة تصاعديًا حسب العمر والاسم: ",
People.stream()
.sorted(مقارنة(حسب العمر).ثممقارنة(حسباسمهم))
.collect(toList()));
قمنا أولاً بإنشاء تعبيرين لامدا، أحدهما يُرجع عمر الشخص المحدد، والآخر يُرجع اسمه. عند استدعاء الأسلوبsorted()، نقوم بدمج هذين التعبيرين بحيث يمكن مقارنة سمات متعددة. تقوم طريقة المقارنة () بإنشاء وإرجاع مقارنة بناءً على العمر، ثم نقوم باستدعاء طريقة المقارنة () على المقارنة التي تم إرجاعها لإنشاء مقارنة مدمجة تقارن العمر والاسم. الإخراج أدناه هو نتيجة الفرز أولاً حسب العمر ثم حسب الاسم.
انسخ رمز الكود كما يلي:
الترتيب تصاعدياً حسب العمر والاسم:
جون - 20
جين - 21
سارة - 21
جريج - 35
كما ترون، يمكن دمج تطبيق Comparator بسهولة باستخدام تعبيرات lambda وفئات الأدوات الجديدة التي يوفرها JDK. دعونا نقدم جامعي أدناه.