JavaRush /مدونة جافا /Random-AR /المقارنة في جافا
Viacheslav
مستوى

المقارنة في جافا

نشرت في المجموعة
فقط الأشخاص الكسالى لم يكتبوا عن المقارنة والمقارنة في Java. أنا لست كسولًا - لذا أطلب منك أن تحب وتفضل شكلًا آخر. آمل ألا يكون غير ضروري. ونعم، هذه المقالة هي الإجابة على السؤال: "هل يمكنك كتابة مقارنة من الذاكرة؟" آمل أنه بعد قراءة هذا المقال، سيتمكن الجميع من كتابة مقارنة من الذاكرة.
المقارنة في جافا - 1
مقدمة من المعروف أن Java هي لغة موجهة للكائنات. نتيجة لذلك، من الشائع في Java التعامل مع الكائنات. ولكن عاجلاً أم آجلاً تنشأ مهمة مقارنة الأشياء وفقًا لبعض المبادئ. لذا، نظرًا: لدينا بعض الرسائل، والتي يتم وصفها بواسطة فئة الرسالة:
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 compiler . دعونا نتذكر أيضًا إضافة الواردات:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
في الطريقة الرئيسية سنقوم بإنشاء عدة رسائل:
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 . كما نعلم، ترث جميع الفئات ضمنيًا من فئة الكائن هذه. وهذا أمر منطقي، لأنه يعبر هذا بشكل أساسي عن المفهوم: "كل شيء هو كائن" ويوفر سلوكًا مشتركًا لجميع الفئات. وتحدد هذه الفئة أن كل فئة لها طريقتان: → hashCode تقوم طريقة hashCode بإرجاع بعض التمثيل الرقمي (int) للكائن كمثال للفئة. ماذا يعني ذلك؟ هذا يعني أنه إذا قمت بإنشاء مثيلين مختلفين لفئة ما، فبما أن المثيلات مختلفة، فيجب أن يكون رمز التجزئة الخاص بها مختلفًا. هذا ما ورد في وصف الطريقة: "بقدر ما هو عملي بشكل معقول، فإن طريقة hashCode المحددة بواسطة فئة الكائن تقوم بإرجاع أعداد صحيحة مميزة لكائنات مميزة" أي، إذا كانت هاتان المثيلتان مختلفتان، فيجب أن يكون لهما اختلافات مختلفة hashCodes. أي أن هذه الطريقة غير مناسبة لمقارنتنا. → يساوي طريقة يساوي تجيب على السؤال "هل الكائنات متساوية" وترجع قيمة منطقية. تحتوي هذه الطريقة على الكود الافتراضي:
public boolean equals(Object obj) {
    return (this == obj);
}
أي أنه بدون تجاوز هذه الطريقة على كائن ما، فإن هذه الطريقة توضح بشكل أساسي ما إذا كانت المراجع إلى الكائن متطابقة أم لا. هذا غير مناسب لرسائلنا، لأننا لسنا مهتمين بروابط للكائن، نحن مهتمون بمعرف الرسالة. وحتى إذا تجاوزنا طريقة التساوي، فإن الحد الأقصى الذي سنحصل عليه هو: "إنهم متساوون" أو "إنهم غير متساوين". لكن هذا لا يكفي بالنسبة لنا لتحديد الترتيب.

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

ما الذي يناسبنا؟ إذا ترجمنا كلمة "قارن" إلى اللغة الإنجليزية في المترجم، فسنحصل على الترجمة "مقارنة". عظيم، إذن نحن بحاجة إلى شخص يقارن. فإذا قارنت هذه المقارنة فإن الذي يقارن هو المقارن. لنفتح Java Api ونبحث عن Comparator هناك . وبالفعل، هناك مثل هذه الواجهة - java.util.Comparator java.util.Comparator وjava.lang.Comparable كما ترون، هناك مثل هذه الواجهة. تقول الفئة التي تنفذها "أقوم بتنفيذ وظيفة لمقارنة الكائنات". الشيء الوحيد الذي يجب تذكره حقًا هو عقد المقارنة، والذي يتم التعبير عنه على النحو التالي:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
الآن دعونا نكتب المقارنة. سنحتاج إلى استيراد java.util.Comparator . بعد الاستيراد، أضف طريقة إلى main: Comparator<Message> comparator = new Comparator<Message>(); بطبيعة الحال، لن ينجح هذا، لأن المقارنة هي واجهة. لذلك، بعد الأقواس سنضيف تلك المتعرجة { }. وسنكتب الطريقة بين القوسين:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
ليس عليك حتى أن تتذكر أن تكتب هذا. والمقارن هو الذي يجري مقارنة، أي يقارن. للإجابة على سؤال ما هو ترتيب الكائنات المقارنة، نعود إلى int. هذا كل شيء، في الواقع. ببساطة وسهولة. كما نرى من المثال، بالإضافة إلى المقارنة، هناك واجهة أخرى - java.lang.Comparable ، والتي يجب علينا عند تنفيذها تحديد طريقة المقارنة . تقول هذه الواجهة أن "الفئة التي تطبق واجهة تسمح بمقارنة مثيلات الفئة." على سبيل المثال، يبدو تطبيق Integer للمقارنة كما يلي:
(x < y) ? -1 : ((x == y) ? 0 : 1)
كيف تتذكر كل هذه الواجهات؟ لأي غرض؟ كل شيء يأتي من اللغة الإنجليزية. قارن - للمقارنة، الذي يقارن هو المقارن (كمسجل مثلا، أي الذي يسجل)، والصفة "مقارنة" هي قابلة للمقارنة. حسنًا، تتم ترجمة عبارة "المقارنة بـ" ليس فقط على أنها مقارنة بـ، ولكن أيضًا على أنها مقارنة بـ. انه سهل. تمت كتابة لغة جافا من قبل أشخاص يتحدثون الإنجليزية، وفي تسمية كل شيء في جافا، تم توجيههم ببساطة باللغة الإنجليزية وكان هناك نوع من المنطق في التسمية. وتصف طريقة CompareTo كيفية مقارنة مثيل فئة ما بمثيلات أخرى. على سبيل المثال، تتم مقارنة السلاسل من الناحية المعجمية ، وتتم مقارنة الأرقام من حيث القيمة.
المقارنة في جافا - 2
جلبت Java 8 بعض التغييرات الرائعة. إذا نظرنا عن كثب إلى واجهة المقارنة، فسنرى أن هناك تعليقًا توضيحيًا فوقها @FunctionalInterface. في الواقع، هذا التعليق التوضيحي مخصص للعلم ويعني أن هذه الواجهة فعالة. هذا يعني أن هذه الواجهة تحتوي على طريقة مجردة واحدة فقط بدون تطبيق. ماذا يعطينا هذا؟ يمكننا كتابة رمز المقارنة الآن مثل هذا:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
بين قوسين هو كيفية تسمية المتغيرات. سوف ترى جافا نفسها ذلك لأن ... إذا كانت هناك طريقة واحدة فقط، فمن الواضح ما هي معلمات الإدخال المطلوبة، وكم عددها، وما هي أنواعها. بعد ذلك، نقول بسهم أننا نريد نقلهم إلى هذا القسم من الكود. بالإضافة إلى ذلك، بفضل Java 8، ظهرت الأساليب الافتراضية في الواجهات - وهي الأساليب التي تظهر افتراضيًا (افتراضيًا) عندما نقوم بتنفيذ واجهة ما. يوجد العديد منها في واجهة المقارنة، على سبيل المثال:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
هناك طريقة أخرى من شأنها أن تجعل التعليمات البرمجية الخاصة بك أكثر نظافة. دعونا نلقي نظرة على المثال أعلاه، حيث وصفنا المقارنة لدينا. ماذا يفعل؟ إنها بدائية تمامًا. إنه ببساطة يأخذ كائنًا ويستخرج منه بعض القيمة القابلة للمقارنة. على سبيل المثال، يقوم Integer بتنفيذ قابلية للمقارنة، لذلك تمكنا من إجراء مقارنة على قيم معرف الرسالة. يمكن أيضًا كتابة دالة المقارنة البسيطة هذه على النحو التالي:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
وهذا يعني، حرفيًا، "لدينا مُقارن يقارن بهذه الطريقة: فهو يأخذ الكائنات، ويصبح قابلاً للمقارنة منها باستخدام طريقة getId()، ويقارن باستخدام 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) المصفوفات أيضًا ملائمة للفرز. مرة أخرى، لنفس السبب المتمثل في الوصول إلى العناصر عن طريق الفهرس. → أحفاد java.util.SortedSet وjava.util.SortedMap كما نتذكر، لا يضمن Set وMap ترتيب تخزين السجلات. ولكن لدينا تطبيقات خاصة تضمن النظام. وإذا كانت عناصر المجموعة لا تطبق java.lang.Comparable، فيمكننا تمرير 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<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));
بدا الكود مخيفًا بعض الشيء، ولكن تم حل المشكلة الآن بشكل صحيح بفضل التنفيذ الصريح لـ TreeMap. يمكنك قراءة المزيد عن المجموعات المختلفة هنا: يمكنك إنشاء المجمع بنفسك. يمكنك قراءة المزيد هنا: "إنشاء مجمع مخصص في Java 8" . ومن المفيد قراءة المناقشة هنا: "قائمة Java 8 المراد تعيينها باستخدام الدفق" .
المقارنة في جافا - 3
المقارنة والمكابس القابلة للمقارنة جيدة. ولكن هناك فارق بسيط مرتبط بهم يستحق التذكر. عندما يقوم أحد الفصول بالفرز، فإنه يحسب أنه يمكنه تحويل الفصل الخاص بك إلى قابل للمقارنة. إذا لم يكن الأمر كذلك، فسوف تتلقى خطأ في وقت التنفيذ. لنلقي نظرة على مثال:
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. بالإضافة إلى ذلك يوصى بالعرض: Yuri Tkach: HashSet وTreeSet - المجموعات رقم 1 - Java المتقدمة
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION