JavaRush /Курсы /Java Multithreading /Comparator, сортировка коллекций

Comparator, сортировка коллекций

Java Multithreading
6 уровень , 1 лекция
Открыта
Comparator, сортировка коллекций - 1

— Привет, Амиго!

— Привет, Билаабо!

— Сегодня будет небольшая, но интересная и полезная тема – сортировки коллекций.

— Сортировка? Я что-то про это слышал.

— Давным-давно каждый программист обязан был уметь писать сортировку. Умел и писал. Но те времена канули в лету. Сегодня написание своей сортировки считается дурным тоном, как и написание всего, что уже было придумано.

В Java (да и других языках программирования) сортировки уже реализованы. Твоя задача – научиться правильно пользоваться тем, что есть.

— Ок.

— У вспомогательного класса Collections есть статический метод sort, который используется для сортировки коллекций, а если точнее – списков. Элементы в коллекциях Map и Set не имеют порядка/номера, значит, и сортировать там нечего.

— Да, я вспомнил, я когда-то уже использовал этот метод для сортировки списка чисел.

— Отлично. Но этот метод гораздо мощнее чем, кажется на первый взгляд. Он может сортировать не только числа, но и любые объекты, по любым критериям. И помогают ему в этом два интерфейса: Comparable и Comparator.

Иногда бывает нужно отсортировать объекты, а не числа. Например, у тебя есть список людей, и ты хочешь отсортировать их по возрасту. Для этого есть интерфейс Comparable.

Давай я сначала покажу тебе пример, и все станет понятнее:

Пример
public class Woman implements Comparable<Woman>
{
public int age;

public Woman(int age) {
this.age = age;
}

public int compareTo(Woman o)
{
return this.age - o.age;
}
}
Пример использования:
public static void main(String[] args )
{
ArrayList<Woman> women = new ArrayList<Woman>();
women.add(new Woman(18));
women.add(new Woman(21));
women.add(new Woman(5));

Collections.sort(women);
}

Чтобы объекты можно было сортировать, сначала нужно научиться их сравнивать. Для этого и используется Comparable. Интерфейс Comparable является generic’ом – т.е. типом с параметром. У него всего один generic-метод – compareTo(T o). В этом методе и происходит сравнение переданного объекта (o) и текущего (this). Т.е. надо переопределить этот метод в своем классе и сравнить в нем текущий объект (this) с переданным.

— А как работает compareTo? Я думал, что он будет возвращать true/false в зависимости от того – больше переданный объект или меньше.

— Тут все немного хитрее. Метод compareTo возвращает не true/false, а значение типа int. На самом деле так сделано для простоты.

Когда компьютеру нужно определить больше ли одно число, чем другое, он просто вычитает из первого числа второе, а потом смотрит, что получилось. Если 0 – числа равны, если получилось число меньше нуля, то второе число больше, а если результат больше нуля, то больше уже первое число.

Тут используется та же логика. Согласно спецификации метод compareTo должен вернуть ноль, если сравниваемые объекты равны. Если метод compareTo вернул число больше нуля, это значит, что наш (this) объект больше, чем переданный. Если метод compareTo вернул число меньше нуля, то объект this меньше чем переданный.

— Немного странно.

— Да, но если ты сравниваешь объекты просто по какому-то параметру-числу, то можешь просто вернуть разницу между ними – вычесть один из другого. Как это и сделано в примере выше.

public int compareTo(Woman o)
{
return this.age - o.age;
}

— Вроде все понятно. Хотя может и не все. Но почти все.

— Отлично. Теперь рассмотрим более практическую задачу. Ты написал крутой сайт по пошиву женской одежды в Китае. Для описания своих пользователей ты используешь класс Woman. Ты даже сделал страницу с таблицей, где можешь посмотреть их всех. Но есть проблема…

Объект Woman содержит у тебя не только возраст, а еще целую кучу данных: имя, фамилию, рост, вес, количество детей, …

В таблице пользователей есть много колонок, и тут встает вопрос: а как сортировать пользователей по разным критериям? По весу, по возрасту, по фамилии?

— Гм. Действительно, часто вижу таблицы с сортировкой колонок. И как это сделать?

— А для этого есть второй интерфейс, о котором я хотел тебе сегодня рассказать – это интерфейс Comparator. И у него тоже есть метод сравнения, только он называется compare и принимает не один параметр, а два: int compare(T o1, T o2). Вот как это работает:

Пример
public class Woman {

public int age;
public int childrenCount;
public int weight;
public int height;
public String name;

public Woman(int age, int childrenCount, int weight, int height, String name) {

    this.age = age;
    this.childrenCount = childrenCount;
    this.weight = weight;
    this.height = height;
    this.name = name;
    }
}
Пример использования:
public static void main(String[] args ) {

    ArrayList<Woman> women = new ArrayList<Woman>();
    women.add(new Woman(18, 0, 45, 170, "Ann"));
    women.add(new Woman(21, 1, 57, 168, "Iren"));
    women.add(new Woman(5, 0, 20, 110, "Angelina"));
    …

    Comparator<Woman> compareByHeight = new Comparator<Woman>() { public int compare(Woman o1, Woman o2) {
    return o1.height - o2.height;
    } };

    Collections.sort(women, compareByHeight);
}

При использовании интерфейса Comparator, логика сравнения пары объектов не прячется внутрь класса/объекта, а реализуется в отдельном классе.

— Т.е. я могу сделать несколько классов, реализующих интерфейс Comparator, но в каждом из них сравнивать разные параметры? В одном – weight, в другом – age, в третьем – height?

— Да, это очень просто и удобно.

Мы просто вызываем метод Collections.sort, передаем туда список объектов и еще специальный объект во втором параметре, который реализует интерфейс Comparator и говорит, как правильно сравнивать пары объектов в процессе сортировки.

— Гм. Вроде все понятно. Дай-ка я сам попробую. Допустим, мне нужно отсортировать пользователей по весу, это будет так:

Пример кода, пользователи сортируются по весу:
Comparator<Woman> compareByWeight = new Comparator<Woman>() {

public int compare(Woman o1, Woman o2) {
 
    return o1.weight - o2.weight;
    }
};

Collections.sort(women, compareByWeight);

— Да, именно так.

— Отлично. А если я хочу отсортировать в обратном порядке?

— А подумать? Ответ очень простой!

— Придумал! Вот так:

Сортировка по возрастанию:
return o1.weight - o2.weight;
Сортировка по убыванию:
return o2.weight – o1.weight;

— Правильно. Молодец.

— А если я хочу сортировать по фамилии? Как сортировать строки, Билаабо?

— А у строк уже реализован метод compareTo, надо просто вызвать его:

Пример кода, пользователи сортируются по имени:
Comparator<Woman> compareByName = new Comparator<Woman>() {

public int compare(Woman o1, Woman o2) {

    return o1.name.compareTo(o2.name);
}
};

Collections.sort(women, compareByName);

— Это был отличный урок, Билаабо, спасибо тебе большое.

— И тебе спасибо, друг!

Комментарии (98)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Kirill Уровень 46
18 октября 2024
Элементы в коллекциях Map и Set не имеют порядка/номера, значит, и сортировать там нечего.

Воу, стопэ! А как же SortedMap, SortedSet, NavigableMap NavigableSet?

Евгений Пикин Уровень 46
19 февраля 2025
Дак это же другие коллекции.
SomeBody098 Уровень 51
29 июля 2024
познавательно )
Long_byte Уровень 43
9 июня 2024
Когда компьютеру нужно определить больше ли одно число, чем другое, он просто вычитает из первого числа второе, а потом смотрит, что получилось. Если 0 – числа равны, если получилось число меньше нуля, то второе число больше, а если результат больше нуля, то больше уже первое число. узнал что то новое для себя
26 марта 2024
Пипец, урок понятен, хороший, но что делают коллекции в курсе Потопоки?
Максим Пыгамов Уровень 36
30 августа 2023
women.sort(Comparator.comparing(o -> o.name)); women.forEach(Woman::printInfo); красота, да и только!
Fl1s Уровень 51
7 августа 2023
Лол, я на другой уровень перейти не могу, кнопка сломалась
k Уровень 39
30 августа 2023
лол
Бромгексин Уровень 38
1 сентября 2024
лол
Ra Уровень 16 Student
29 июля 2023
PhanSca Уровень 48
12 мая 2023
Я прям представляю, как приходишь на собеседование, там тебя спрашивают - "Расскажите, как устроена быстрая сортировка?". А ты такой "— Давным-давно каждый программист обязан был уметь писать сортировку. Умел и писал. Но те времена канули в лету. Сегодня написание своей сортировки считается дурным тоном, как и написание всего, что уже было придумано."
Kurama Уровень 50
9 ноября 2022
А как же:

Collections.sort(women, (o1,o2) -> o1.weight - o2.weight );
Griboed Уровень 30
23 марта 2023
Так это то же самое, что в лекции. Comparator - функциональный интерфейс, т.к. содержит один нереализованный метод. Таким образом, создание анонимного объекта-наследника интерфейса Comparator мы можем записать при помощи лямбда-выражения, в котором указано, какие объекты передаются в нереализованный метод (в нашем случае метод int compare(T o1, T o1) ), и какое значение данный метод должен вернуть: (o1,o2) -> o1.weight - o2.weight
Kurama Уровень 50
26 марта 2023
Да, так я и говорю, зачем мы учили анонимные внутренние классы и лямбда-функции, чтоб потом их не использовать
imagine_hehe Уровень 40
10 мая 2023
А разве мы учили лямбда-функции? Да, там что-то немного в синтаксе было, но это не считается. Да и вообще вроде говорили на эту тему уже, везде используются разные версии и соответственно в старых версиях есть не весь нынешний функционал. Если ты знаешь как это написать без лямбд и анонимных классов, не думаю, что возникнут проблемы с использованием различного синтаксического сахара, а вот наоборот, возможно.
Kurama Уровень 50
15 мая 2023
19 уровень Синтаксиса называется Лямбда-функции
MineJavaAcc Уровень 42
4 ноября 2022
Читал я как-то статью, где расписано что вот так как в лекции возвращать вот такое сравнение нельзя: public int compareTo(Woman o) { return this.age - o.age; } , а надо пользоваться готовыми compare, которые написаны под все примитивные типы или прямо писать со знаком сравнения. Например мы сравниваем не женщин а планеты и не возраст а координаты, тогда вот такие большие числа там вполне могут встречаться: int s1 = 2_000_000_000; int s2 = -2_000_000_000; System.out.println("s1 - s2 = " + (s1 - s2) + " ; Integer.compare(s1,s2) = " + Integer.compare(s1,s2) + " ; (s1>s2) = " + (s1>s2) + " ; (s2>s1) = " + (s2>s1)); то есть s1 - s2 должно быть положительным, но происходит переполнение типа: Вывод: s1 - s2 = -294967296 ; Integer.compare(s1,s2) = 1 ; (s1>s2) = true ; (s2>s1) = false