Чем отличается Comparator от Comparable?
Интерфейс Comparable определяет естественный порядок среди объектов. Java Collections Framework активно использует этот порядок. По нему упорядочены элементы SortedSet / SortedMap , им упорядочиваются элементы списков и массивов в методе sort().
Порядок определяется единственным методом compareTo . Отрицательный результат означает что текущий объект «меньше» чем переданный параметром, 0 – равен, положительный – больше. Рекомендуется чтобы равные с точки зрения equals объекты всегда были равны с точки зрения compareTo .
С математической точки зрения это должен быть линейный порядок. Он требует выполнения четырех свойств:
1. Антирефлексивность: x.compareTo(x) всегда 0 ;
2. Антисимметричность: если x.compareTo(y) > 0 , то y.compareTo(x) < 0 ;
3. Транзитивность: если x.compareTo(y) > 0 и y.compareTo(z) > 0 , то x.compareTo(z) > 0 ;
4. Полнота: отношение определено для любых объектов класса (кроме null ).
Интерфейс Comparator – это логика Comparable, вынесенная в отдельный объект. Компаратор реализует паттерн Стратегия. Большинство платформенных методов, использующих Comparable имеют перегруженный вариант с не-comparable объектом и внешним компаратором.
Comparator бывает полезен, когда класс предоставлен сторонней библиотекой, и нет возможности его менять. Другой случай – особая логика упорядочивания, не свойственная классу объектов в общем, но нужная для отдельной ситуации.
Кроме основного метода compare() , в компараторе есть набор утилитарных методов для комбинирования и модификации компараторов. Все они возвращают новый компаратор, позволяя сделать его иммутабельным.
Чем отличается comparable от comparator java
В прошлой теме была рассмотрена работа коллекции TreeSet, типизированной объектами String. При добавлении новых элементов объект TreeSet автоматически проводит сортировку, помещая новый объект на правильное для него место. Однако со строками все понятно. А что если бы мы использовали не строки, а свои классы, например, следующий класс Person:
class Person < private String name; Person(String name)< this.name=name; >String getName() >
Объект TreeSet мы не сможем типизировать данным классом, поскольку в случае добавления объектов TreeSet не будет знать, как их сравнивать, и следующий кусок кода не будет работать:
TreeSet people = new TreeSet(); people.add(new Person("Tom"));
При выполнении этого кода мы столкнемся с ошибкой, которая скажет, что объект Person не может быть преобразован к типу java.lang.Comparable.
Для того, чтобы объекты Person можно было сравнить и сортировать, они должны применять интерфейс Comparable . При применении интерфейса он типизируется текущим классом. Применим его к классу Person:
class Person implements Comparable < private String name; Person(String name)< this.name = name; >String getName() public int compareTo(Person p) < return name.compareTo(p.getName()); >>
Интерфейс Comparable содержит один единственный метод int compareTo(E item) , который сравнивает текущий объект с объектом, переданным в качестве параметра. Если этот метод возвращает отрицательное число, то текущий объект будет располагаться перед тем, который передается через параметр. Если метод вернет положительное число, то, наоборот, после второго объекта. Если метод возвратит ноль, значит, оба объекта равны.
В данном случае мы не возвращаем явным образом никакое число, а полагаемся на встроенный механизм сравнения, который есть у класса String. Но мы также можем определить и свою логику, например, сравнивать по длине имени:
public int compareTo(Person p)
Теперь мы можем типизировать TreeSet типом Person и добавлять в дерево соответствующие объекты:
TreeSet people = new TreeSet(); people.add(new Person("Tom"));
Интерфейс Comparator
Однако перед нами может возникнуть проблема, что если разработчик не реализовал в своем классе, который мы хотим использовать, интерфейс Comparable, либо реализовал, но нас не устраивает его функциональность, и мы хотим ее переопределить? На этот случай есть еще более гибкий способ, предполагающий применение интерфейса Comparator.
Интерфейс Comparator содержит ряд методов, ключевым из которых является метод compare() :
public interface Comparator < int compare(T a, T b); // остальные методы >
Метод compare также возвращает числовое значение — если оно отрицательное, то объект a предшествует объекту b, иначе — наоборот. А если метод возвращает ноль, то объекты равны. Для применения интерфейса нам вначале надо создать класс компаратора, который реализует этот интерфейс:
class PersonComparator implements Comparator < public int compare(Person a, Person b)< return a.getName().compareTo(b.getName()); >>
Здесь опять же проводим сравнение по строкам. Теперь используем класс компаратора для создания объекта TreeSet:
PersonComparator pcomp = new PersonComparator(); TreeSet people = new TreeSet(pcomp); people.add(new Person(«Tom»)); people.add(new Person(«Nick»)); people.add(new Person(«Alice»)); people.add(new Person(«Bill»)); for(Person p : people)
Для создания TreeSet здесь используется одна из версий конструктора, которая в качестве параметра принимает компаратор. Теперь вне зависимости от того, реализован ли в классе Person интерфейс Comparable, логика сравнения и сортировки будет использоваться та, которая определена в классе компаратора.
Сортировка по нескольким критериям
Начиная с JDK 8 в механизм работы компараторов были внесены некоторые дополнения. В частности, теперь мы можем применять сразу несколько компараторов по принципу приоритета. Например, изменим класс Person следующим образом:
class Person < private String name; private int age; public Person(String n, int a)< name=n; age=a; >String getName() int getAge() >
Здесь добавлено поле для хранения возраста пользователя. И, допустим, нам надо отсортировать пользователей по имени и по возрасту. Для этого определим два компаратора:
class PersonNameComparator implements Comparator < public int compare(Person a, Person b)< return a.getName().compareTo(b.getName()); >> class PersonAgeComparator implements Comparator < public int compare(Person a, Person b)< if(a.getAge()>b.getAge()) return 1; else if(a.getAge() < b.getAge()) return -1; else return 0; >>
Интерфейс компаратора определяет специальный метод по умолчанию thenComparing , который позволяет использовать цепочки компараторов для сортировки набора:
Comparator pcomp = new PersonNameComparator().thenComparing(new PersonAgeComparator()); TreeSet people = new TreeSet(pcomp); people.add(new Person(«Tom», 23)); people.add(new Person(«Nick»,34)); people.add(new Person(«Tom»,10)); people.add(new Person(«Bill»,14)); for(Person p : people)
Bill 14 Nick 34 Tom 10 Tom 23
В данном случае сначала применяется сортировка по имени, а потом по возрасту.
Интерфейсы Comparable и Comparator
Для того чтобы объекты можно было сравнить и сортировать, они должны реализовать параметризованный интерфейс Comparable .
Интерфейс Comparable содержит один единственный метод int compareTo(T item) , который сравнивает текущий объект с объектом, переданным в качестве параметра.
Если этот метод возвращает отрицательное число, то текущий объект будет располагаться перед тем, который передается через параметр. Если метод вернет положительное число, то, наоборот, после второго объекта. Если метод возвращает ноль, значит, оба объекта равны.
Рассмотрим пример реализации интерфейса Comparable :
public class Person implements Comparable < private String firstName; private String lastName; private int age; public Person(String firstName, String lastName, int age) < this.firstName = firstName; this.lastName = lastName; this.age = age; >public String getFirstName() < return firstName; >public void setFirstName(String firstName) < this.firstName = firstName; >public String getLastName() < return lastName; >public void setLastName(String lastName) < this.lastName = lastName; >public int getAge() < return age; >public void setAge(int age) < this.age = age; >@Override public int compareTo(Person anotherPerson) < int anotherPersonAge = anotherPerson.getAge(); return this.age - anotherPersonAge; >@Override public String toString() < return "Personpublic class ComparePersonDemo < public static void main(String[] args) < SortedSetset = new TreeSet<>(); set.add(new Person("Саша", "Иванов", 36)); set.add(new Person("Маша", "Петрова", 23)); set.add(new Person("Даша", "Сидорова", 34)); set.add(new Person("Вася", "Иванов", 25)); set.forEach(System.out::println); > >
2. Интерфейс Comparator
Если класс по какой-то причине не может реализовать интерфейс Comparable , или же просто нужен другой вариант сравнения, используется интерфейс Comparator .
Интерфейс содержит метод int compare(T o1, T o2) , который должен быть реализован классом, реализующим компаратор.
Метод compare возвращает числовое значение — если оно отрицательное, то объект o1 предшествует объекту o2 , иначе — наоборот. А если метод возвращает ноль, то объекты равны.
Для применения интерфейса нам вначале надо создать класс компаратора, который реализует этот параметризованный интерфейс.
Рассмотрим пример использования интерфейса Comparator :
import java.util.Comparator; public class PersonComparator implements Comparator < @Override public int compare(Person o1, Person o2) < return o1.getLastName().compareTo(o2.getLastName()); >>
import java.util.SortedSet; import java.util.TreeSet; public class PersonComparatorDemo < public static void main(String[] args) < PersonComparator personComparator = new PersonComparator(); SortedSetset = new TreeSet<>(personComparator); set.add(new Person("Саша", "Иванов", 36)); set.add(new Person("Маша", "Петрова", 23)); set.add(new Person("Даша", "Сидорова", 34)); set.add(new Person("Вася", "Иванов", 25)); //Обратите внимание - было добавлено 4 элемента, но распечатано 3 set.forEach(System.out::println); > >
То же самое перепишем с использованием метода comparing() интерфейса Comparator :
import java.util.Comparator; import java.util.SortedSet; import java.util.TreeSet; public class PersonComparingDemo < public static void main(String[] args) < ComparatorpersonComparator = Comparator.comparing(Person::getLastName).thenComparing(Person::getAge); SortedSet set = new TreeSet<>(personComparator); set.add(new Person("Саша", "Иванов", 36)); set.add(new Person("Маша", "Петрова", 23)); set.add(new Person("Даша", "Сидорова", 34)); set.add(new Person("Вася", "Иванов", 25)); set.forEach(System.out::println); > >
- Интерфейс Collection
- Структуры данных
- Интерфейс List и класс ArrayList
- Интерфейс Set и классы HashSet, LinkedHashSet
- Интерфейс SortedSet и класс TreeSet
- Интерфейс NavigableSet
- Интерфейсы Queue и Deque
- Класс ArrayDeque
- Класс LinkedList
- Класс PriorityQueue
- Принцип PECS
- Интерфейс Iterator
- Интерфейс ListIterator
- Отображения Map
- Класс Collections
- Backed Collections
- Legacy Classes
- Задания
Comparable и Comparator
Два новых интерфейса java.lang.Comparable и java.util.Comparator были добавлены в версии Java 5. Использование данных интерфейcов в своих приложениях позволяет упорядочивать (сортировать) данные.
Интерфейс Comparable
В интерфейсе Comparable объявлен только один метод compareTo (Object obj), предназначенный для упорядочивания объектов класса. Данный метод удобно использовать для сортировки списков или массивов объектов.
Метод compareTo (Object obj) сравнивает вызываемый объект с obj. В отличие от метода equals, который возвращает true или false, compareTo возвращает:
- 0, если значения равны;
- Отрицательное значение (обычно -1), если вызываемый объект меньше obj;
- Положительное значение (обычно +1), если вызываемый объект больше obj.
Если типы объектов не совместимы при сравнении, то compareTo (Object obj) может вызвать исключение ClassCastException. Необходимо помнить, что аргумент метода compareTo имеет тип сравниваемого объекта класса.
Обычные классы Byte, Short, Integer, Long, Double, Float, Character, String уже реализуют интерфейс Comparable.
Пример реализации интерфейса Comparable
package test; import java.util.TreeSet; class Compare implements Comparable < String str; int num; String TEMPLATE = "num = %d, str = '%s'"; Compare(String str, int num) < this.str = str; this.num = num; >@Override public int compareTo(Object obj) < Compare entry = (Compare) obj; int result = str.compareTo(entry.str); if(result != 0) return result; result = num - entry.num; if(result != 0) return (int) result / Math.abs( result ); return 0; >@Override public String toString() < return String.format(TEMPLATE, num, str); >> public class Example < public static void main(String[] args) < TreeSetdata = new TreeSet(); data.add(new Compare("Начальная школа" , 234)); data.add(new Compare("Начальная школа" , 132)); data.add(new Compare("Средняя школа" , 357)); data.add(new Compare("Высшая школа" , 246)); data.add(new Compare("Музыкальная школа", 789)); for (Compare e : data) System.out.println(e.toString()); > >
Результат выполнения программы:
num = 246, str = 'Высшая школа' num = 789, str = 'Музыкальная школа' num = 132, str = 'Начальная школа' num = 234, str = 'Начальная школа' num = 357, str = 'Средняя школа'
В примере значения сортируются сначала по полю str (по алфавиту), а затем по num в методе compareTo. Это хорошо видно по двум строкам с одинаковыми значения str и различными num. Чтобы изменить порядок сортировки значения str (в обратном порядке), необходимо внести небольшие изменения в метод compareTo.
@Override public int compareTo(Object obj) < int result = ((Comp)obj).str.compareTo(str); if(result != 0) return result; result = entry.number - number; if(result != 0) < return (int) result / Math.abs(result); return 0; >
Интерфейс Comparator : compare, compareTo
В интерфейсе Comparator объявлен метод compare (Object obj1, Object obj2), который позволяет сравнивать между собой два объекта. На выходе метод возвращает значение 0, если объекты равны, положительное значение или отрицательное значение, если объекты не тождественны.
Метод может вызвать исключение ClassCastException, если типы объектов не совместимы при сравнении. Простой пример реализации интерфейса Comparator:
package test; import java.util.TreeSet; import java.util.Iterator; import java.util.Comparator; class Compare implements Comparator < @Override public int compare(String obj1, String obj2) < return obj1.compareTo(obj2); >> public class Example < public static void main(String[] args) < TreeSetdata = new TreeSet(); data.add(new String("Змей Горыныч" )); data.add(new String("Баба Яга" )); data.add(new String("Илья Муромец" )); data.add(new String("Алеша Попович" )); data.add(new String("Соловей Разбойник")); Iterator i = data.iterator(); while(i.hasNext()) System.out.println(i.next()); > >
Результат выполнения программы:
Алеша Попович Баба Яга Змей Горыныч Илья Муромец Соловей Разбойник
Усложним пример, и реализуем несколько видов сортировки. Для этого создадим класс Product с полями name, price и quantity.
class Product < private String name; private float price; private float quantity; public Product (String name, float price, float quantity) < this.name = name; this.price = price; this.quantity = quantity; >public String getName() < return name; >public void setName(String name) < this.name = name; >public float getPrice() < return price; >public void setPrice(float price) < this.price = price; >public float getQuantity() < return quantity; >public void setQuantity(float quantity) < this.quantity = quantity; >@Override public String toString() < return "Наименование '" + name + "', цена - " + String.valueOf (price) + ", количество - " + String.valueOf (quantity); >>
Создадим два класса (SortedByName, SortedByPrice), реализующих интерфейс Comparator для сортировки объектов по названию и по цене :
// сортировка по названию class SortedByName implements Comparator < public int compare(Product obj1, Product obj2) < String str1 = obj1.getName(); String str2 = obj2.getName(); return str1.compareTo(str2); >> // сортировка по цене class SortedByPrice implements Comparator < public int compare(Product obj1, Product obj2) < float price1 = obj1.getPrice(); float price2 = obj2.getPrice(); if (price1 >price2) < return 1; >else if (price1 < price2) < return -1; >else < return 0; >> >
Пример использования Arrays.sort :
public class Example < public static void main(String[] args) < Product[] products = new Product[3]; // заполним объект Product содержимым products[0] = new Product("Молоко", 35.56f,900.00f); products[1] = new Product("Кофе" ,199.50f, 90.00f); products[2] = new Product("Чай" , 78.50f,150.00f); // выведем данные без сортировки System.out.println("~~~~~ без сортировки"); for(Product product : products) System.out.println(product.toString()); // Сортировка по цене Arrays.sort(products, new SortedByPrice()); System.out.println("\n~~~ сортировка по цене"); for(Product product : products) System.out.println(product.toString()); // Сортировка по названию Arrays.sort(products, new SortedByName()); System.out.println("\n~~~ сортировка по названию"); for(Product product : products) System.out.println(product.toString()); >>
Результат выполнения программы:
~~~~~ без сортировки Наименование 'Молоко', цена - 35.56, количество - 900.0 Наименование 'Кофе', цена - 199.5, количество - 90.0 Наименование 'Чай', цена - 78.5, количество - 150.0 ~~~ сортировка по цене Наименование 'Молоко', цена - 35.56, количество - 900.0 Наименование 'Чай', цена - 78.5, количество - 150.0 Наименование 'Кофе', цена - 199.5, количество - 90.0 ~~~ сортировка по названию Наименование 'Кофе', цена - 199.5, количество - 90.0 Наименование 'Молоко', цена - 35.56, количество - 900.0 Наименование 'Чай', цена - 78.5, количество - 150.0
Для сортировки объектов были реализованы два независимых компаратора по наименованию и по цене (SortedByName и SortedByPrice). Сортировка выполняется с помощью класса Arrays, у которого есть метод sort. Данный метод в качестве второго аргумента принимает тип компаратора.
Arrays.sort(T[] arg1, Comparator arg2);
Можно использовать также метод sort класса Collections, который в качестве первого входного аргумента принимает список объектов:
Collections.sort(List arg1, Comparator arg2);
Отличие интерфейсов Comparator и Comparable
Интерфейс Comparable используется только для сравнения объектов класса, в котором данный интерфейс реализован. Т.е. interface Comparable определяет логику сравнения объекта определенного ссылочного типа внутри своей реализации (по правилам разработчика).
Comparator представляет отдельную реализацию и ее можно использовать многократно и с различными классами. Т.е. interface Comparator позволяет создавать объекты, которые будут управлять процессом сравнения (например при сортировках).