JavaRush /Java блог /Random UA /Comparator в Java

Comparator в Java

Стаття з групи Random UA
Вітання! Сьогодні ми поговоримо про порівняння об'єктів. Хм… Але ми, начебто, про це вже не раз розмовляли? :/ Ми знаємо як працює оператор “ ==”, а також про методи equals()та hashCode(). Порівняння – не зовсім про це. Раніше ми мали на увазі, скоріше, «перевірку об'єктів на рівність». Comparator в Java - 1А порівняння об'єктів один з одним може мати зовсім іншу мету! Найочевидніше з них — сортування. Я думаю, якщо тобі скажуть відсортувати список ArrayList<>чисел чи рядків, ти з цим упораєшся без проблем:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       String name1 = "Марійка";
       String name2 = "Саша";
       String name3 = "Даша";

       List<String> names = new ArrayList<>();
       names.add(name1);
       names.add(name2);
       names.add(name3);

       Collections.sort(names);
       System.out.println(names);
   }
}
Виведення в консоль:

[Даша, Маша, Саша]
Відмінно, якщо ти згадав про клас Collectionsта його метод sort(). З числами, гадаю, теж проблем не виникне. А ось тобі завдання складніше:
public class Car {

   private int manufactureYear;
   private String model;
   private int maxSpeed;

   public Car(int manufactureYear, String model, int maxSpeed) {
       this.manufactureYear = manufactureYear;
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   //...геттеры, сеттеры, toString()

}

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);
   }
}
Ось дуже просто: клас Carта 3 його об'єкти. Будь такий добрий, відсортуй автомобілі у списку! Ти, напевно, запитаєш: «А як їх треба відсортувати?». За назвою, роком випуску, максимальною швидкістю? Чудове питання. Ми не знаємо зараз, як треба сортувати об'єкти класу Car. І, природно, Java теж цього не знає! При спробі передати метод Collections.sort()список об'єктів Carми отримаємо помилку:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(20012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);

       //ошибка компилятора!
       Collections.sort(cars);
   }
}
Звідки мови знати, як саме сортувати написані тобою об'єкти? Це залежить від завдань вашої програми. Ми повинні навчити Java порівнювати ці об'єкти. Причому порівнювати так, як нам це потрібно. Для цього в Java є спеціальний інструмент - інтерфейс Comparable. З англійської це перекладається як «порівняльний». Щоб наші об'єкти Carможна було порівнювати один з одним і якось сортувати, клас повинен імплементувати цей інтерфейс і реалізувати його єдиний метод compareTo():
public class Car implements Comparable<Car> {

   private int manufactureYear;
   private String model;
   private int maxSpeed;

   public Car(int manufactureYear, String model, int maxSpeed) {
       this.manufactureYear = manufactureYear;
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   @Override
   public int compareTo(Car o) {
       return 0;
   }

   //...геттеры, сеттеры, toString()

}
Зверни увагу:ми вказали інтерфейс Comparable<Car>, а не просто Comparable. Це типізований інтерфейс, тобто вимагає вказівки конкретного класу, з яким він пов'язаний. В принципі, <Car>можна з інтерфейсу і прибрати, але тоді він за умовчанням порівнює об'єкти Object. Замість методу compareTo(Car o)у нас у класі буде:
@Override
   public int compareTo(Object o) {
       return 0;
   }
Нам, звичайно, набагато простіше працювати з Car. Усередині методу compareTo()реалізуємо логіку порівняння машин. Допустимо, нам потрібно відсортувати їх за роком випуску. Напевно, ти звернув увагу, що метод compareTo()повертає значення int, а чи не boolean. Нехай тебе це не дивує. Справа в тому, що порівняння двох об'єктів дає нам 3 можливі варіанти:
  • а < b
  • a > b
  • a == b.
Вже booleanє лише 2 значення - true і false, що незручно для порівняння об'єктів. Зі intвсе набагато простіше. Якщо значення, що повертається > 0, значить a > b. Якщо результат compareTo < 0, значить а < b. А якщо результат == 0, отже два об'єкта рівні: a == b. Навчити наш клас сортувати машини за роком випуску — найпростіше:
@Override
public int compareTo(Car o) {
   return this.getManufactureYear() - o.getManufactureYear();
}
Що тут відбувається? Ми беремо один об'єкт машини ( this), рік випуску цієї машини та віднімаємо з нього рік випуску іншої машини (тою, з якою порівнюємо об'єкт). Якщо рік випуску першої машини більше, метод поверне int > 0. Отже, машина this >машини о. Якщо навпаки — рік випуску другої машини ( о) більше, отже метод поверне негативне число, отже, о > this. Ну а якщо вони рівні, метод поверне 0. Такого простого механізму вже достатньо, щоб сортувати колекції об'єктів Car! Більше нічого не потрібно робити. Ось будь ласка:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);

       //тут раньше была ошибка
       Collections.sort(cars);
       System.out.println(cars);
   }
}
Виведення в консоль:

[Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310}, 
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350}, 
Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290}]
Машини відсортовані як слід! :) Comparator в Java - 2У яких випадках треба використовувати Comparable? Реалізований Comparableметод порівняння називають «natural ordering» — природним сортуванням. Це тому, що в методі compareTo()ти описуєш найпоширеніший спосіб порівняння, який використовуватиметься для об'єктів цього класу у твоїй програмі. Natural Ordering вже є у Java. Наприклад, Java знає, що рядки найчастіше сортують за абеткою, а числа - за зростанням їх значення. Тому якщо викликати на списку чисел чи рядків метод sort(), вони і будуть відсортовані. Якщо в нашій програмі машини в більшості випадків порівнюватимуть і сортуватимуться за роком випуску, значить, варто визначити для них натуральне сортування за допомогою інтерфейсу Comparable<Car>та методуcompareTo(). Але якщо нам цього недостатньо? Давай уявімо, що наша програма не така проста. У більшості випадків натуральне сортування машин (ми встановабо його за роком випуску) нас влаштовує. Але іноді серед наших клієнтів трапляються любителі швидкої їзди. Якщо ми готуємо для них каталог автомобілів на вибір, їх потрібно впорядкувати за максимальною швидкістю. Comparator в Java - 3Наприклад, таке сортування нам потрібне у 15% випадків. Цього явно недостатньо, щоб встановити натуральне сортування для Carшвидкості замість року випуску. Але й ігнорувати 15% клієнтів ми не можемо. Що ж нам робити? Тут нам допомагає інший інтерфейс — Comparator. Так само, як і Comparableвін типизований. А в чому ж різниця? Comparableробить наші об'єкти "порівняними" і створює для них найбільш природний порядок сортування, який буде використовуватися в більшості випадків. Comparator— це окремий клас-«порівняльник» (переклад трохи корявий, але зрозумілий). Якщо нам потрібно реалізувати якесь специфічне сортування, нам необов'язково лізти до класу Carта змінювати логіку compareTo(). Натомість ми можемо створити окремий клас-компаратор у нашій програмі та навчити його робити потрібне нам сортування!
import java.util.Comparator;

public class MaxSpeedCarComparator implements Comparator<Car> {

   @Override
   public int compare(Car o1, Car o2) {
       return o1.getMaxSpeed() - o2.getMaxSpeed();
   }
}
Як бачиш, наш Comparatorдоволі простий. Всього один метод compare()- це метод інтерфейсу Comparator, який обов'язково потрібно реалізувати. Він приймає на вхід два об'єкти Carі звичним нам чином (відніманням) порівнює їх максимальну швидкість. Як і compareTo(), він повертає число int, принцип порівняння той самий. Як нам користуватися цим? Дуже просто:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);

       Comparator speedComparator = new MaxSpeedCarComparator();
       Collections.sort(cars, speedComparator);

       System.out.println(cars);
   }
}
Виведення в консоль:

[Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290}, 
Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310}, 
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350}]
Ми просто створюємо об'єкт-компаратор і передаємо його метод Collections.sort()разом зі списком, який треба відсортувати. Отримавши на вхід компаратор, метод sort()не використовуватиме природне сортування, визначену методі compareTo()класу Car. Натомість він застосує алгоритм сортування з переданого йому компаратора. Які переваги нам це дає? По-перше, сумісність із написаним кодом. Ми створабо новий, специфічний метод сортування, і при цьому зберегли діючий, який використовуватиметься в більшості випадків. Ми взагалі не чіпали клас Car. Він як був Comparable, так і залишився:
public class Car implements Comparable<Car> {

   private int manufactureYear;
   private String model;
   private int maxSpeed;

   public Car(int manufactureYear, String model, int maxSpeed) {
       this.manufactureYear = manufactureYear;
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   @Override
   public int compareTo(Car o) {
       return this.getManufactureYear() - o.getManufactureYear();
   }

   //...геттеры, сеттеры, toString()

}
По-друге, гнучкість. Ми можемо додавати скільки завгодно сортувань. Скажімо, відсортувати машини за кольором, швидкістю, вагою, або з того, скільки разів ця машина використовувалася у фільмах про Бетмена. Достатньо лише створити додатковий Comparator. От і все! Сьогодні ти вивчив два дуже важливі механізми, які часто використовуватимеш у реальних проектах на роботі. Але, як відомо, теорія без практики – ніщо. Тому, саме час закріпити знання та вирішити кілька завдань! :)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ