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 повернув число більше нуля, це означає, що наш (цей) об'єкт більше, ніж переданий. Якщо метод compareTo повернув число менше нуля, то об'єкт це менше ніж переданий.

— Трохи дивно.

— Так, але якщо ти порівнюєш об'єкти просто за якимось параметром-числом, то можеш просто повернути різницю між ними – відняти один від одного. Як це і зроблено на прикладі вище.

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

— Це був чудовий урок, Білаабо, дякую тобі велике.

— І тобі дякую, друже!