CodeGym /مدونة جافا /Random-AR /واجهة المقارنة لجافا
John Squirrels
مستوى
San Francisco

واجهة المقارنة لجافا

نشرت في المجموعة
الكسالى ليسوا الوحيدين الذين يكتبون عن المقارنات والمقارنات في جافا. أنا لست كسولًا، لذا يرجى الحب والتذمر بشأن تفسير آخر. آمل ألا يكون غير ضروري. ونعم، هذه المقالة هي إجابة السؤال: " هل يمكنك كتابة مقارن من الذاكرة؟ " أتمنى أن يتمكن الجميع من كتابة مقارن من الذاكرة بعد قراءة هذا المقال. واجهة مقارنة جافا - 1

مقدمة

كما تعلمون، جافا هي لغة كائنية التوجه. ونتيجة لذلك، فمن المعتاد التعامل مع الكائنات في جافا. ولكن عاجلاً أم آجلاً، ستواجه مهمة مقارنة الأشياء بناءً على بعض الخصائص. على سبيل المثال : لنفترض أن لدينا بعض الرسائل التي وصفها الفصل Message:

public static class Message {
    private String message;
    private int id;
        
    public Message(String message) {
        this.message = message;
        this.id = new Random().nextInt(1000);
    }
    public String getMessage() {
        return message;
    }
    public Integer getId() {
        return id;
    }
    public String toString() {
        return "[" + id + "] " + message;
    }
}
ضع هذه الفئة في برنامج التحويل البرمجي Tutorialspoint Java . لا تنس إضافة بيانات الاستيراد أيضًا:

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
في mainالطريقة، قم بإنشاء عدة رسائل:

public static void main(String[] args){
    List<Message> messages = new ArrayList();
    messages.add(new Message("Hello, World!"));
    messages.add(new Message("Hello, Sun!"));
    System.out.println(messages);
}
دعونا نفكر فيما سنفعله إذا أردنا المقارنة بينهما؟ على سبيل المثال، نريد الفرز حسب المعرف. ولإنشاء ترتيب، نحتاج إلى مقارنة الكائنات بطريقة أو بأخرى لكي نفهم أي كائن يجب أن يأتي أولاً (أي الأصغر) وأي كائن يجب أن يتبعه (أي الأكبر). لنبدأ بفصل مثل java.lang.Object . نحن نعلم أن جميع الفئات ترث Objectالفئة ضمنيًا. وهذا منطقي لأنه يعكس مفهوم أن "كل شيء هو كائن" ويوفر سلوكًا مشتركًا لجميع الطبقات. تنص هذه الفئة على أن كل فئة لها طريقتان: → تقوم hashCode الطريقة hashCodeبإرجاع بعض intالتمثيل الرقمي ( ) للكائن. ماذا يعني ذالك؟ هذا يعني أنه إذا قمت بإنشاء مثيلين مختلفين لفئة ما، فيجب أن يكون لهما hashCodes مختلفة. يقول وصف الطريقة ما يلي: "بقدر ما هو عملي إلى حد معقول، فإن طريقة hashCode المحددة بواسطة فئة Object تقوم بإرجاع أعداد صحيحة مميزة لكائنات مميزة". وبعبارة أخرى، بالنسبة إلى instances مختلفين، يجب أن يكون هناك hashCodes مختلفان. أي أن هذه الطريقة غير مناسبة لمقارنتنا. → equals. تجيب الطريقة equalsعلى السؤال "هل هذه الكائنات متساوية؟" وترجع " boolean." افتراضيًا، تحتوي هذه الطريقة على الكود التالي:

public boolean equals(Object obj) {
    return (this == obj);
}
أي أنه إذا لم يتم تجاوز هذه الطريقة، فإنها توضح بشكل أساسي ما إذا كانت مراجع الكائنات متطابقة أم لا. ليس هذا ما نريده لرسائلنا، لأننا مهتمون بمعرفات الرسائل، وليس مراجع الكائنات. وحتى لو تجاوزنا هذه equalsالطريقة، فإن أقصى ما يمكن أن نأمله هو معرفة ما إذا كانا متساويين أم لا. وهذا لا يكفي بالنسبة لنا لتحديد الترتيب. فماذا نحتاج إذن؟ نحن بحاجة إلى شيء يقارن. ومن يقارن هو Comparator. افتح Java API وابحث عن Comparator . في الواقع، هناك java.util.Comparatorواجهة java.util.Comparator and java.util.Comparable كما ترون، مثل هذه الواجهة موجودة. تقول الفئة التي تنفذها: "أقوم بتنفيذ طريقة لمقارنة الكائنات." الشيء الوحيد الذي عليك أن تتذكره حقًا هو عقد المقارنة، والذي يتم التعبير عنه على النحو التالي:

Comparator returns an int according to the following rules: 
  • It returns a negative int if the first object is smaller
  • It returns a positive int if the first object is larger
  • It returns zero if the objects are equal
الآن دعونا نكتب المقارنة. سنحتاج إلى استيراد java.util.Comparator. بعد عبارة الاستيراد، أضف ما يلي إلى mainالطريقة: Comparator<Message> comparator = new Comparator<Message>(); بالطبع لن ينجح هذا، لأنها Comparatorواجهة. لذلك نضيف الأقواس المعقوفة {}بعد القوسين. اكتب الطريقة التالية داخل الأقواس:

public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
لا تحتاج حتى إلى تذكر التهجئة. والمقارن هو الذي يجري المقارنة، أي أنه يقارن. للإشارة إلى الترتيب النسبي للكائنات، نعيد int. هذا هو الأساس. جميل وسهل. كما ترون من المثال، بالإضافة إلى المقارنة، هناك واجهة أخرى — java.lang.Comparableوالتي تتطلب منا تنفيذ compareToالطريقة. تقول هذه الواجهة: "الفصل الذي يطبقني يجعل من الممكن مقارنة مثيلات الفصل." على سبيل المثال، Integerتنفيذ compareTo هو كما يلي:

(x < y) ? -1 : ((x == y) ? 0 : 1)
قدمت Java 8 بعض التغييرات الرائعة. إذا ألقيت نظرة فاحصة على الواجهة Comparator، فسترى التعليق @FunctionalInterfaceالتوضيحي الموجود فوقها. هذا التعليق التوضيحي مخصص لأغراض إعلامية ويخبرنا أن هذه الواجهة فعالة. هذا يعني أن هذه الواجهة تحتوي على طريقة مجردة واحدة فقط، وهي طريقة بدون تطبيق. ماذا يعطينا هذا؟ الآن يمكننا كتابة كود المقارنة مثل هذا:

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
نحن نسمي المتغيرات بين قوسين. سترى Java أنه نظرًا لوجود طريقة واحدة فقط، فإن العدد المطلوب وأنواع معلمات الإدخال تكون واضحة. ثم نستخدم عامل السهم لتمريرها إلى هذا الجزء من الكود. والأكثر من ذلك، بفضل Java 8، لدينا الآن طرق افتراضية في الواجهات. تظهر هذه الطرق افتراضيًا عندما نقوم بتنفيذ واجهة ما. الواجهة Comparatorلديها عدة. على سبيل المثال:

Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
هناك طريقة أخرى من شأنها أن تجعل التعليمات البرمجية الخاصة بك أكثر نظافة. ألق نظرة على المثال أعلاه، حيث قمنا بتعريف المقارنة لدينا. ماذا تعمل، أو ماذا تفعل؟ إنها بدائية تمامًا. إنه ببساطة يأخذ كائنًا ويستخرج بعض القيمة "القابلة للمقارنة". على سبيل المثال، IntegerImplements comparable، لذلك نحن قادرون على إجراء عملية CompareTo على قيم حقول معرف الرسالة. يمكن كتابة دالة المقارنة البسيطة هذه على النحو التالي:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
بمعنى آخر، لدينا "a" Comparatorالذي يقارن على النحو التالي: فهو يأخذ الأشياء، ويستخدم الطريقة getId()للحصول على "a" Comparableمنها، ثم يستخدم compareToللمقارنة. وليس هناك المزيد من الهياكل الرهيبة. وأخيرا، أريد أن أشير إلى ميزة أخرى. يمكن أن تكون بالسلاسل المقارنات. على سبيل المثال:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

طلب

تبين أن الإعلان عن المقارنة أمر منطقي تمامًا، ألا تعتقد ذلك؟ الآن نحن بحاجة إلى معرفة كيف وأين نستخدمها. → Collections.sort(java.util.Collections) يمكننا بالطبع فرز المجموعات بهذه الطريقة. ولكن ليس كل مجموعة، قوائم فقط. لا يوجد شيء غير عادي هنا، لأن القوائم هي نوع المجموعات التي يمكنك من خلالها الوصول إلى العناصر من خلال فهرسها. وهذا يسمح باستبدال العنصر الثاني بالعنصر الثالث. ولهذا السبب فإن طريقة الفرز التالية مخصصة للقوائم فقط:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) من السهل أيضًا فرز المصفوفات. مرة أخرى، لنفس السبب، يتم الوصول إلى عناصرها عن طريق الفهرس. → Descendants of java.util.SortedSet and java.util.SortedMap سوف تتذكر ذلك Setولا Mapتضمن الترتيب الذي يتم به تخزين العناصر. ولكن لدينا تطبيقات خاصة تضمن الطلب. وإذا لم يتم تنفيذ عناصر المجموعة java.util.Comparable، فيمكننا تمرير a Comparatorإلى مُنشئها:

Set<Message> msgSet = new TreeSet(comparator);
Stream API في Stream API، التي ظهرت في Java 8، تتيح لك المقارنات تبسيط العمل مع عناصر الدفق. على سبيل المثال، لنفترض أننا بحاجة إلى سلسلة من الأرقام العشوائية من 0 إلى 999، بما في ذلك:

Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
يمكننا أن نتوقف هنا، ولكن هناك مشاكل أكثر إثارة للاهتمام. على سبيل المثال، لنفترض أنك بحاجة إلى إعداد ملف Map، حيث يكون المفتاح هو معرف الرسالة. بالإضافة إلى ذلك، نريد فرز هذه المفاتيح، لذلك سنبدأ بالكود التالي:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
في الواقع حصلنا على HashMapهنا. وكما نعلم، فإنه لا يضمن أي أمر. ونتيجة لذلك، فإن عناصرنا، التي تم فرزها حسب المعرف، تفقد ترتيبها ببساطة. ليس جيدا. سيتعين علينا تغيير جامعنا قليلاً:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
بدأ الرمز يبدو مخيفًا بعض الشيء، ولكن تم الآن حل المشكلة بشكل صحيح. اقرأ المزيد عن المجموعات المختلفة هنا: يمكنك إنشاء المجمع الخاص بك. اقرأ المزيد هنا: "إنشاء مجمع مخصص في Java 8" . وستستفيد من قراءة المناقشة هنا: "قائمة Java 8 للتعيين باستخدام الدفق" .

فخ السقوط

Comparatorوهي Comparableجيدة. ولكن هناك فارق بسيط يجب أن تتذكره. عندما يقوم الفصل بالفرز، فإنه يتوقع أنه يمكن تحويل فصلك إلى ملف Comparable. إذا لم يكن الأمر كذلك، فسوف تتلقى خطأ في وقت التشغيل. لنلقي نظرة على مثال:

SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
يبدو أنه لا يوجد شيء خاطئ هنا. لكن في الواقع، في مثالنا، سيفشل بسبب وجود خطأ: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable وكل ذلك لأنه حاول فرز العناصر (إنه SortedSetفي النهاية)...لكنه لم يستطع. لا تنس هذا عند العمل مع SortedMapو SortedSet.

المزيد من القراءة:

تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION