CodeGym /Java блог /Случаен /Интерфейсът за сравнение на Java
John Squirrels
Ниво
San Francisco

Интерфейсът за сравнение на Java

Публикувано в групата
Мързеливите не са единствените, които пишат за Comparators и сравнения в Java. Не съм мързелив, така че моля за още едно обяснение. Надявам се, че няма да е излишно. И да, тази статия е отговорът на въпроса: „ Можете ли да напишете компаратор по памет? “ Надявам се, че всеки ще може да напише компаратор по памет, след като прочете тази статия. Интерфейс на Javas Comparator - 1

Въведение

Както знаете, Java е обектно-ориентиран език. В резултат на това е обичайно да се манипулират обекти в Java. Но рано or късно се сблъсквате със задачата да сравнявате обекти въз основа на няHowва характеристика. Например : Да предположим, че имаме няHowво съобщение, описано от 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;
    }
}
Поставете този клас в Java компилатора на Tutorialspoint . Не забравяйте да добавите и инструкциите за импортиране:

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);
}
Нека помислим Howво бихме направor, ако искаме да ги сравним? Например, искаме да сортираме по id. И за да създадем ред, трябва по няHowъв начин да сравним обектите, за да разберем кой обект трябва да е първи (т.е. по-малкият) и кой следва (т.е. по-големият). Нека започнем с клас като java.lang.Object . Знаем, че всички класове имплицитно наследяват Objectкласа. И това има смисъл, защото отразява концепцията, че „всичко е обект“ и осигурява общо поведение за всички класове. Този клас диктува, че всеки клас има два метода: → hashCode Методът hashCodeвръща някои числови (int) представяне на обекта. Какво означава това? Това означава, че ако създадете два различни екземпляра на клас, тогава те трябва да имат различни hashCodes. Описанието на метода казва толкова много: „Доколкото е разумно практично, методът hashCode, дефиниран от клас Object, връща отделни цели числа за отделни обекти“. С други думи, за две различни instances, трябва да има различни hashCodes. Тоест този метод не е подходящ за нашето сравнение. → equals. Методът equalsотговаря на въпроса "равни ли са тези обекти?" и връща boolean." По подразбиране този метод има следния code:

public boolean equals(Object obj) {
    return (this == obj);
}
Тоест, ако този метод не е заменен, той по същество казва дали препратките към обекти съвпадат or не. Това не е, което искаме за нашите съобщения, защото се интересуваме от идентификатори на съобщения, а не от препратки към обекти. И дори да отменим equalsметода, най-многото, на което можем да се надяваме, е да научим дали са равни. И това не е достатъчно, за да определим реда. И така, от Howво се нуждаем тогава? Имаме нужда от нещо, което да се сравнява. Този, който сравнява, е 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. Това е общо взето. Хубаво и лесно. Както можете да видите от примера, освен Comparator, има още един интерфейс — java.lang.Comparable, който изисква да внедрим compareToметода. Този интерфейс казва: "клас, който ме внедрява, прави възможно сравняването на екземпляри на класа." Например Integerвнедряването на compareTo е Howто следва:

(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 въведе някои хубави промени. Ако погледнете по-отблизо интерфейса Comparator, ще видите @FunctionalInterfaceанотацията над него. Тази анотация е с информационна цел и ни казва, че този интерфейс е функционален. Това означава, че този интерфейс има само 1 абстрактен метод, който е метод без реализация. Какво ни дава това? Сега можем да напишем codeа на компаратора така:

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Наименуваме променливите в скоби. Java ще види, че тъй като има само един метод, необходимият брой и типове входни параметри са ясни. След това използваме оператора стрелка, за да ги предадем на тази част от codeа. Нещо повече, благодарение на Java 8 вече имаме методи по подразбиране в интерфейсите. Тези методи се появяват по подразбиране, когато внедрим интерфейс. Интерфейсът Comparatorима няколко. Например:

Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Има друг метод, който ще направи вашия code по-чист. Разгледайте примера по-горе, където дефинирахме нашия компаратор. Какво прави? Доста е примитивно. Той просто взема обект и извлича няHowва стойност, която е „сравнима“. Например 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());

Приложение

Обявяването на компаратор се оказва доста логично, не мислите ли? Сега трябва да видим How и къде да го използваме. → 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, където ключът е идентификатор на съобщение. Освен това искаме да сортираме тези ключове, така че ще започнем със следния code:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Всъщност получаваме HashMapтук. И Howто знаем, това не гарантира ниHowъв ред. В резултат на това нашите елементи, които бяха сортирани по id, просто губят реда си. Не е добре. Ще трябва да променим малко нашия колектор:

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