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) {
this.age = 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));

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);

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

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