java.lang.Comparable и java.util.Comparator
- java.lang.Comparable и java.util.Comparator
- Введение
- java.lang.Comparable
- java.util.Comparator
- Требования
- Применение
- Collection и Array
- Структуры данных
- Stream
- Полезные ссылки
Введение
Как уже было сказано в статье про интерфейсы, интерфейс — это определение поведения. Умение объектов сравнивать себя с друг с другом — это точно такое же поведение.
Сравнение используется различными алгоритмами от сортировки и двоичного поиска до поддержания порядка в сортированных коллекциях вроде java.util.TreeMap
.
Это повдение можно добавить как в непосредственно классы, с которыми мы работаем, так и объявить специальный класс, умеющий сравнивать два переданных объекта и решать кто из них больше, меньше или они вовсе равны.
Такие классы-судьи называются компараторы.
java.lang.Comparable
Для добавления поведения сравнения непосредственно классам, с которыми мы работаем, существует интерфейс java.lang.Comparable
(от англ. сравнимый, сопоставимый):
public interface Comparable<T> { /** * Compares this object with the specified object for order. Returns a * negative integer, zero, or a positive integer as this object is less * than, equal to, or greater than the specified object. * * <p>The implementor must ensure <tt>sgn(x.compareTo(y)) == * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This * implies that <tt>x.compareTo(y)</tt> must throw an exception iff * <tt>y.compareTo(x)</tt> throws an exception.) * * <p>The implementor must also ensure that the relation is transitive: * <tt>(x.compareTo(y)>0 && y.compareTo(z)>0)</tt> implies * <tt>x.compareTo(z)>0</tt>. * * <p>Finally, the implementor must ensure that <tt>x.compareTo(y)==0</tt> * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for * all <tt>z</tt>. * * <p>It is strongly recommended, but <i>not</i> strictly required that * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>. Generally speaking, any * class that implements the <tt>Comparable</tt> interface and violates * this condition should clearly indicate this fact. The recommended * language is "Note: this class has a natural ordering that is * inconsistent with equals." * * <p>In the foregoing description, the notation * <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical * <i>signum</i> function, which is defined to return one of <tt>-1</tt>, * <tt>0</tt>, or <tt>1</tt> according to whether the value of * <i>expression</i> is negative, zero or positive. * * @param o the object to be compared. * @return a negative integer, zero, or a positive integer as this object * is less than, equal to, or greater than the specified object. * * @throws NullPointerException if the specified object is null * @throws ClassCastException if the specified object's type prevents it * from being compared to this object. */ public int compareTo(T o); }
Интерфейс добавляет только один метод, который принимает объект и возвращает результат сравнения:
- 0, если два объекта равны.
- отрицательное число, если объект меньше того, с которым его сравнивают. Обычно возвращают значение
-1
. - положительное число, если объект больше того, с которым его сравнивают. Обычно возвращают значение
1
.
По контракту метода при работе с null
должно быть выброшено исключение java.lang.NullPointerException
.
Во многих классах интерфейс java.lang.Comparable
уже реализован, например, в классах java.lang.Integer
, java.lang.String
и т.д.
Когда имеет смысл задуматься о реализации этого интерфейса в вашем классе? Тогда, когда определен естественный порядок у объектов класса. Например, в числах!
Для примера научим объекты класса Person
, этой рабочей лошадке всех примеров в интернете, сравниваться между собой:
public class Person implements Comparable<Person> { private String name; private int age; private LocalDate birthDate; Person(String name, int age, LocalDate birthDate) { this.age = age; this.name = name; this.birthDate = birthDate; } public String getName() { return name; } public int getAge() { return age; } public LocalDate getBirthDate() { return birthDate; } public int compareTo(Person p) { return name.compareTo(p.getName()); } }
В данной реализации сравнение объектов будет происходить по именам лексеграфически.
Но что делать, если вас не устраивает текущая реализация? Например, вы хотите сравнивать наших Person
по возрасту, а не имени! Или вы попали в ситуацию, когда разработчик вообще не предусмотрел реализацию java.lang.Comparable
в своем классе, а вам надо как-то добавить это поведение, уметь сравнивать объекты. Или ситуация такова, что вам необходимо реализовать несколько видов сравнений в разных случаях. На такой случай существуют компараторы
, те самые классы-судьи.
java.util.Comparator
Компараторы — это отдельные классы, реализующие интерфейс java.util.Comparator
.
Интерфейс java.util.Comparator
содержит ряд методов, ключевым из которых является метод compare
:
public interface Comparator<E> { /** * Compares its two arguments for order. Returns a negative integer, * zero, or a positive integer as the first argument is less than, equal * to, or greater than the second.<p> * * In the foregoing description, the notation * <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical * <i>signum</i> function, which is defined to return one of <tt>-1</tt>, * <tt>0</tt>, or <tt>1</tt> according to whether the value of * <i>expression</i> is negative, zero or positive.<p> * * The implementor must ensure that <tt>sgn(compare(x, y)) == * -sgn(compare(y, x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This * implies that <tt>compare(x, y)</tt> must throw an exception if and only * if <tt>compare(y, x)</tt> throws an exception.)<p> * * The implementor must also ensure that the relation is transitive: * <tt>((compare(x, y)>0) && (compare(y, z)>0))</tt> implies * <tt>compare(x, z)>0</tt>.<p> * * Finally, the implementor must ensure that <tt>compare(x, y)==0</tt> * implies that <tt>sgn(compare(x, z))==sgn(compare(y, z))</tt> for all * <tt>z</tt>.<p> * * It is generally the case, but <i>not</i> strictly required that * <tt>(compare(x, y)==0) == (x.equals(y))</tt>. Generally speaking, * any comparator that violates this condition should clearly indicate * this fact. The recommended language is "Note: this comparator * imposes orderings that are inconsistent with equals." * * @param o1 the first object to be compared. * @param o2 the second object to be compared. * @return a negative integer, zero, or a positive integer as the * first argument is less than, equal to, or greater than the * second. * @throws NullPointerException if an argument is null and this * comparator does not permit null arguments * @throws ClassCastException if the arguments' types prevent them from * being compared by this comparator. */ int compare(T o1, T o2); // остальные методы }
Метод также возвращает числовое значение — если оно отрицательное, то объект o1
считается меньше, предшествует объекту o2
, иначе — наоборот. А если метод возвращает ноль, то объекты равны.
Компаратор на все тот же Person
, но сравнивающий по именам:
class PersonBirthDateComparator implements Comparator<Person> { public int compare(Person a, Person b) { return a.getBirthDate().compareTo(b.getBirthDate()); } }
Далее везде, где вам необходимо поведение сравнения по датам рождения вы явно спрашиваете PersonBirthDateComparator
через вызов compare
.
Надо отметить, что многие классы умеют работать и с компараторами, и с классами, реализующими java.lang.Comparable
, например, java.util.TreeMap
:
Map<Person, String> treeMap = new TreeMap<>(); // 1 Map<Person, String> birthDateTreeMap = new TreeMap<>(new PersonBirthDateComparator()); // 2
В первом случае дерево TreeMap
будет строиться с помощью сравнения по именам, во втором же мы явно передали компаратор, отвечающий за сравнения и будет использован именно он.
Требования
Помните три важных свойства сравнивающей функции: рефлексивность
(сравнение элемента с самим собой всегда даёт 0
), антисимметричность
(сравнение A с B и B с A должны дать разный знак) и транзитивность
(если сравнение A с B и B с C выдаёт одинаковый результат, то и сравнение A с C должно выдать такой же).
При несоблюдении этих свойств результат работы алгоритмов сортировки и структур данных, основанных на сравнении, как например, java.util.TreeMap
, будет неверным.
Применение
Collection и Array
Классы java.util.Collections
и java.util.Arrays
предоставляют метод sort
для сортировки с помощью компаратора списков и массивов соответственно:
public static <T> void sort(List<T> list, Comparator<? super T> c) { // ... }
и
public static <T> void sort(T[] a, Comparator<? super T> c) { // ... }
Структуры данных
Структуры данных, типа java.util.TreeMap
или java.util.TreeSet
работают только с элементами, реализующими java.lang.Comparable
, или требуют в конструкторе компаратор.
Set<Message> messages = new TreeSet(comparator);
Вопрос:
Небольшое лирическое отступление от темы!
А что будет, если мы создадим TreeMap
и ключом укажем класс, который не реализует java.lang.Comparable
и при этом не передадим компаратор?
class Student { private int age; public Student(int age) { this.age = age; } // getters and setters } // psvm Map<Person, String> treeMap = new TreeMap<>();
Ответ:
Объект TreeMap
будет создан, но при первом же добавлении элемента будет выброшено исключение, так как для работы TreeMap
необходимо, чтобы ключи умели сравниваться, мы же не добавили этого поведения и не предоставили компаратор, поэтому работать TreeMap
не сможет:
Exception in thread "main" java.lang.ClassCastException: class Student cannot be cast to class java.lang.Comparable
Подробнее о работе TreeMap.
Stream
В Java 8
появились Stream
-ы, позволяющие использовать компараторы прямо в цепочке вычислений:
stream .sorted(Comparator.naturalOrder()) .forEach(e -> System.out.println(e));
Вопрос:
Я написал свой компаратор, работающий с целыми числами:
class IntegerComparator implements Comparator<Integer> { public int compare(Integer a, Integer b) { return a - b; } }
Видите ли вы ошибку?
Ответ:
Компаратор реализован неправильно, так как не учитывает возможности переполнения (overflow) у java.lang.Integer
.
int a = -2000000000; int b = 2000000000; System.out.println(a - b); // prints "294967296"
Помните об этом, когда работаете с числами в Java
!
Также помните о существовании NaN
у Double
и Float
. Оно не больше, не меньше и не равно никакому другому числу.
Полезные ссылки
- Тагир Валеев Пишите компараторы правильно
- Timur Batyrshinov Compare и Comparator в Java
- Subtraction comparator
- Overflow and Underflow in Java
Stop(Id, Name)
is a java class, and i want to store these stop objects in a java.util.Set
and those objects should be sorted according to the Id
of Stop
.
this is my comparator
public class StopsComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
// TODO Auto-generated method stub
Stop stop1 = (Stop)o1;
Stop stop2 = (Stop)o2;
return stop1.getStopId().compareTo(stop2.getStopId());
}
}
private Set<Stop> stops = new TreeSet<Stop>(new StopsComparator());
but its not giving correct result?
Bhesh Gurung
50.1k22 gold badges91 silver badges141 bronze badges
asked Dec 24, 2011 at 18:30
Ramesh KothaRamesh Kotha
8,22617 gold badges65 silver badges89 bronze badges
2
Does Stop implement an equals method that works on the same field as your comparator? If not then that will lead to problems. You also might want to switch to have your object implement Comparable (although that wouldn’t fix the problem you’re seeing).
Once you implement an equals()
method, then you should also implement a hashCode()
method that works on the same field.
Findbugs would have probably told you these things. Its extremely useful.
answered Dec 24, 2011 at 18:41
BillRobertson42BillRobertson42
12.5k4 gold badges40 silver badges57 bronze badges
The following code works for me —
public class Stop {
private Long id;
private String name;
public Stop(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Stop{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
private static class StopComparator implements Comparator<Stop> {
public int compare(Stop o1, Stop o2) {
return o1.getId().compareTo(o2.getId());
}
}
public static void main(String[] args) {
Set<Stop> set = new TreeSet<Stop>(new StopComparator());
set.add(new Stop(102L, "name102"));
set.add(new Stop(66L, "name66"));
set.add(new Stop(72L, "name72"));
System.out.println(set);
}
}
prints —
[Stop{id=66, name=’name66′}, Stop{id=72, name=’name72′}, Stop{id=102,
name=’name102′}]
Ofc you need to implement equals
and hashcode
so that class behaves consistently in each Set
implementation, but for TreeSet
this should work as is since TreeSet
relies on compareTo
method while performing add
, remove
or contains
operations (instead of equals
like HashSet
).
answered Dec 24, 2011 at 19:04
2
This is from the Comparator
docs:
The ordering imposed by a comparator c on a set of elements S is said to be consistent with equals if and only if c.compare(e1, e2)==0 has the same boolean value as e1.equals(e2) for every e1 and e2 in S.
Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map). Suppose a sorted set (or sorted map) with an explicit comparator c is used with elements (or keys) drawn from a set S. If the ordering imposed by c on S is inconsistent with equals, the sorted set (or sorted map) will behave «strangely.» In particular the sorted set (or sorted map) will violate the general contract for set (or map), which is defined in terms of equals.
I would recommend to try implementing equals
and hashCode
.
answered Dec 24, 2011 at 18:37
Bhesh GurungBhesh Gurung
50.1k22 gold badges91 silver badges141 bronze badges
В этом посте будет обсуждаться, как сортировать список объектов с помощью Comparator в Java.
А Comparator — это функция сравнения, которая упорядочивает наборы объектов, которые не имеют естественного порядка. Разработчик этого класса должен переопределить абстрактный метод compare()
определено в java.util.Comparator
, который сравнивает два своих аргумента для порядка. Значение, возвращаемое compare()
метод определяет положение первого объекта относительно второго объекта.
- Если
compare()
возвращает отрицательное целое число, первый аргумент меньше второго. - Если
compare()
возвращает ноль, первый аргумент равен второму. - Если
compare()
возвращает положительное целое число, первый аргумент больше второго.
Есть несколько способов реализовать компараторы в Java:
1. Передайте компаратор в качестве аргумента sort()
метод
Компараторы, если они передаются методу сортировки (например, Collections.sort
(а также Arrays.sort
), позволяют точно контролировать порядок сортировки. В следующем примере мы получаем Comparator
что сравнивает Person
предметы по возрасту.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
import java.util.*; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return «{« + «name='» + name + »’ + «, age=» + age + ‘}’; } public String getName() { return name; } public int getAge() { return age; } } class Main { public static void main(String[] args) { List<Person> persons = new ArrayList<>(Arrays.asList( new Person(«John», 15), new Person(«Sam», 25), new Person(«Will», 20), new Person(«Dan», 20), new Person(«Joe», 10) )); Collections.sort(persons, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.getAge() — p2.getAge(); } }); System.out.println(persons); } } |
Скачать Выполнить код
результат:
[{name=’Joe’, age=10}, {name=’John’, age=15}, {name=’Will’, age=20}, {name=’Dan’, age=20}, {name=’Sam’, age=25}]
С Comparator
является функциональным интерфейсом, его можно использовать в качестве цели назначения для лямбда-выражения или ссылки на метод. Следовательно,
Collections.sort(persons, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.getAge() — p2.getAge(); } }); |
можно переписать как:
Collections.sort(persons, (p1, p2) -> p1.getAge() — p2.getAge()); |
Java 8 представила несколько улучшений для Comparator
интерфейс. Теперь Comparator имеет статические методы, такие как comparing()
, который может легко создавать компараторы для сравнения некоторых конкретных значений объектов. Например, для получения Comparator
что сравнивает Person
объектов по их возрасту, мы можем сделать:
Comparator<Person> byAge = Comparator.comparing(Person::getAge); Collections.sort(persons, byAge); |
Приведенный выше код будет сортировать список людей только по age
поле. Если два человека имеют одинаковый возраст, их относительный порядок в отсортированном списке не является фиксированным. Таким образом, предпочтительно сравнивать объекты, используя несколько полей, чтобы избежать таких случаев.
Как сравнить объекты с несколькими полями?
1. Мы можем легко отсортировать список людей сначала по age
а затем name
, как показано ниже. Теперь для лиц одного возраста порядок определяется по имени человека.
Collections.sort(persons, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { if (p1.getAge() != p2.getAge()) { return p1.getAge() — p2.getAge(); } return p1.getName().compareTo(p2.getName()); } }); |
Скачать Выполнить код
Обратите внимание, что мы просто уменьшили значение int
примитивный тип age
друг от друга, а для объекта String встроенный метод сравнения compareTo()
используется. Как правило, эта цепочка может продолжаться и дальше, включая и другие свойства.
2. Мы также можем сделать это с лямбда-выражениями, используя .thenComparing()
метод, который эффективно объединяет два сравнения в одно:
Comparator<Person> byAge = Comparator.comparing(Person::getAge); Comparator<Person> byName = Comparator.comparing(Person::getName); Collections.sort(persons, byAge.thenComparing(byName)); |
Скачать Выполнить код
3. Мы также можем использовать Guava ComparisonChain для выполнения оператора цепного сравнения, как показано ниже:
Collections.sort(persons, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return ComparisonChain.start() .compare(p1.getAge(), p2.getAge()) .compare(p1.getName(), p2.getName()) .result(); } }); |
Скачать код
Возвращаемое значение compare()
будет иметь тот же знак, что и первый ненулевой результат сравнения в цепочке, или будет равен нулю, если все результаты сравнения были равны нулю. Обратите внимание, что ComparisonChain
прекращает вызывать свои входы compareTo
а также compare
методы, как только один из них возвращает ненулевой результат.
4. Мы также можем использовать CompareToBuilder класса библиотеки Apache Commons Lang, чтобы помочь в реализации Comparator.compare()
метод. Чтобы использовать этот класс, напишите следующий код:
Collections.sort(persons, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return new CompareToBuilder() .append(p1.getAge(), p2.getAge()) .append(p1.getName(), p2.getName()) .toComparison(); } }); |
Скачать код
Значения сравниваются в том порядке, в котором они добавляются к построителю. Если какое-либо сравнение возвращает ненулевой результат, то это значение будет возвращено функцией toComparison()
, и все последующие сравнения пропускаются.
Мы можем даже реализовать Comparator
в отдельный класс, а затем передать экземпляр этого класса в sort()
метод. Это показано ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
import java.util.*; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return «{« + «name='» + name + »’ + «, age=» + age + ‘}’; } public String getName() { return name; } public int getAge() { return age; } } class MyComparator implements Comparator<Person> { @Override public int compare(Person p1, Person p2) { if (p1.getAge() != p2.getAge()) { return p1.getAge() — p2.getAge(); } return p1.getName().compareTo(p2.getName()); } } class Main { public static void main(String[] args) { List<Person> persons = new ArrayList<>(Arrays.asList( new Person(«John», 15), new Person(«Sam», 25), new Person(«Will», 20), new Person(«Dan», 20), new Person(«Joe», 10) )); Collections.sort(persons, new MyComparator()); System.out.println(persons); } } |
Скачать Выполнить код
результат:
[{name=’Joe’, age=10}, {name=’John’, age=15}, {name=’Dan’, age=20}, {name=’Will’, age=20}, {name=’Sam’, age=25}]
3. Передать компаратор в List.sort()
метод
Java 8 представила несколько улучшений для List
интерфейс. В настоящее время List
имеет собственный метод сортировки sort() который сортирует список в соответствии с порядком, индуцированным указанным Comparator
. Это показано ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import java.util.*; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return «{« + «name='» + name + »’ + «, age=» + age + ‘}’; } public String getName() { return name; } public int getAge() { return age; } } class Main { public static void main(String[] args) { List<Person> persons = new ArrayList<>(Arrays.asList( new Person(«John», 15), new Person(«Sam», 25), new Person(«Will», 20), new Person(«Dan», 20), new Person(«Joe», 10) )); persons.sort(Comparator.comparing(Person::getAge) .thenComparing(Comparator.comparing(Person::getName))); System.out.println(persons); } } |
Скачать Выполнить код
результат:
[{name=’Joe’, age=10}, {name=’John’, age=15}, {name=’Dan’, age=20}, {name=’Will’, age=20}, {name=’Sam’, age=25}]
4. Передать компаратор в Stream.sorted()
метод
Мы также можем передать наш компаратор в sorted()
метод Stream
класс, который возвращает поток, состоящий из элементов этого потока, отсортированных в соответствии с предоставленным Comparator
. Вот рабочий пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return «{« + «name='» + name + »’ + «, age=» + age + ‘}’; } public String getName() { return name; } public int getAge() { return age; } } class Main { public static void main(String[] args) { List<Person> persons = new ArrayList<>(Arrays.asList( new Person(«John», 15), new Person(«Sam», 25), new Person(«Will», 20), new Person(«Dan», 20), new Person(«Joe», 10) )); persons = persons.stream() .sorted(Comparator.comparing(Person::getAge) .thenComparing(Comparator.comparing(Person::getName))) .collect(Collectors.toList()); System.out.println(persons); } } |
Скачать Выполнить код
результат:
[{name=’Joe’, age=10}, {name=’John’, age=15}, {name=’Dan’, age=20}, {name=’Will’, age=20}, {name=’Sam’, age=25}]
Это все, что касается сортировки списка объектов с помощью Comparator в Java.
Продолжить чтение:
Сортировка списка объектов с помощью Comparable в Java
Employees : [Employee{id=1010, name=’Rajeev’, salary=100000.0, joiningDate=2010-07-10}, Employee{id=1004, name=’Chris’, salary=95000.5, joiningDate=2017-03-19}, Employee{id=1015, name=’David’, salary=134000.0, joiningDate=2017-09-28}, Employee{id=1009, name=’Steve’, salary=100000.0, joiningDate=2016-05-18}]
Employees (Sorted by Name) : [Employee{id=1004, name=’Chris’, salary=95000.5, joiningDate=2017-03-19}, Employee{id=1015, name=’David’, salary=134000.0, joiningDate=2017-09-28}, Employee{id=1010, name=’Rajeev’, salary=100000.0, joiningDate=2010-07-10}, Employee{id=1009, name=’Steve’, salary=100000.0, joiningDate=2016-05-18}]
Employees (Sorted by Salary) : [Employee{id=1004, name=’Chris’, salary=95000.5, joiningDate=2017-03-19}, Employee{id=1010, name=’Rajeev’, salary=100000.0, joiningDate=2010-07-10}, Employee{id=1009, name=’Steve’, salary=100000.0, joiningDate=2016-05-18}, Employee{id=1015, name=’David’, salary=134000.0, joiningDate=2017-09-28}]
Employees (Sorted by JoiningDate) : [Employee{id=1010, name=’Rajeev’, salary=100000.0, joiningDate=2010-07-10}, Employee{id=1009, name=’Steve’, salary=100000.0, joiningDate=2016-05-18}, Employee{id=1004, name=’Chris’, salary=95000.5, joiningDate=2017-03-19}, Employee{id=1015, name=’David’, salary=134000.0, joiningDate=2017-09-28}]
Employees (Sorted by Name in descending order) : [Employee{id=1009, name=’Steve’, salary=100000.0, joiningDate=2016-05-18}, Employee{id=1010, name=’Rajeev’, salary=100000.0, joiningDate=2010-07-10}, Employee{id=1015, name=’David’, salary=134000.0, joiningDate=2017-09-28}, Employee{id=1004, name=’Chris’, salary=95000.5, joiningDate=2017-03-19}]
Employees (Sorted by Salary and Name) : [Employee{id=1004, name=’Chris’, salary=95000.5, joiningDate=2017-03-19}, Employee{id=1010, name=’Rajeev’, salary=100000.0, joiningDate=2010-07-10}, Employee{id=1009, name=’Steve’, salary=100000.0, joiningDate=2016-05-18}, Employee{id=1015, name=’David’, salary=134000.0, joiningDate=2017-09-28}]
A comparator interface is used to order the objects of user-defined classes. A comparator object is capable of comparing two objects of the same class. Following function compare obj1 with obj2.
Syntax:
public int compare(Object obj1, Object obj2):
Suppose we have an Array/ArrayList of our own class type, containing fields like roll no, name, address, DOB, etc, and we need to sort the array based on Roll no or name?
Method 1: One obvious approach is to write our own sort() function using one of the standard algorithms. This solution requires rewriting the whole sorting code for different criteria like Roll No. and Name.
Method 2: Using comparator interface- Comparator interface is used to order the objects of a user-defined class. This interface is present in java.util package and contains 2 methods compare(Object obj1, Object obj2) and equals(Object element). Using a comparator, we can sort the elements based on data members. For instance, it may be on roll no, name, age, or anything else.
Method of Collections class for sorting List elements is used to sort the elements of List by the given comparator.
public void sort(List list, ComparatorClass c)
To sort a given List, ComparatorClass must implement a Comparator interface.
How do the sort() method of Collections class work?
Internally the Sort method does call Compare method of the classes it is sorting. To compare two elements, it asks “Which is greater?” Compare method returns -1, 0, or 1 to say if it is less than, equal, or greater to the other. It uses this result to then determine if they should be swapped for their sort.
Example
Java
import
java.io.*;
import
java.lang.*;
import
java.util.*;
class
Student {
int
rollno;
String name, address;
public
Student(
int
rollno, String name, String address)
{
this
.rollno = rollno;
this
.name = name;
this
.address = address;
}
public
String toString()
{
return
this
.rollno +
" "
+
this
.name +
" "
+
this
.address;
}
}
class
Sortbyroll
implements
Comparator<Student> {
public
int
compare(Student a, Student b)
{
return
a.rollno - b.rollno;
}
}
class
Sortbyname
implements
Comparator<Student> {
public
int
compare(Student a, Student b)
{
return
a.name.compareTo(b.name);
}
}
class
GFG {
public
static
void
main(String[] args)
{
ArrayList<Student> ar =
new
ArrayList<Student>();
ar.add(
new
Student(
111
,
"Mayank"
,
"london"
));
ar.add(
new
Student(
131
,
"Anshul"
,
"nyc"
));
ar.add(
new
Student(
121
,
"Solanki"
,
"jaipur"
));
ar.add(
new
Student(
101
,
"Aggarwal"
,
"Hongkong"
));
System.out.println(
"Unsorted"
);
for
(
int
i =
0
; i < ar.size(); i++)
System.out.println(ar.get(i));
Collections.sort(ar,
new
Sortbyroll());
System.out.println(
"nSorted by rollno"
);
for
(
int
i =
0
; i < ar.size(); i++)
System.out.println(ar.get(i));
Collections.sort(ar,
new
Sortbyname());
System.out.println(
"nSorted by name"
);
for
(
int
i =
0
; i < ar.size(); i++)
System.out.println(ar.get(i));
}
}
Output
Unsorted 111 Mayank london 131 Anshul nyc 121 Solanki jaipur 101 Aggarwal Hongkong Sorted by rollno 101 Aggarwal Hongkong 111 Mayank london 121 Solanki jaipur 131 Anshul nyc Sorted by name 101 Aggarwal Hongkong 131 Anshul nyc 111 Mayank london 121 Solanki jaipur
By changing the return value inside the compare method, you can sort in any order that you wish to, for example: For descending order just change the positions of ‘a’ and ‘b’ in the above compare method.
Sort collection by more than one field
In the previous example, we have discussed how to sort the list of objects on the basis of a single field using Comparable and Comparator interface But, what if we have a requirement to sort ArrayList objects in accordance with more than one field like firstly, sort according to the student name and secondly, sort according to student age.
Example
Java
import
java.util.ArrayList;
import
java.util.Collections;
import
java.util.Comparator;
import
java.util.Iterator;
import
java.util.List;
class
Student {
String Name;
int
Age;
public
Student(String Name, Integer Age)
{
this
.Name = Name;
this
.Age = Age;
}
public
String getName() {
return
Name; }
public
void
setName(String Name) {
this
.Name = Name; }
public
Integer getAge() {
return
Age; }
public
void
setAge(Integer Age) {
this
.Age = Age; }
@Override
public
String toString()
{
return
"Customer{"
+
"Name="
+ Name +
", Age="
+ Age +
'}'
;
}
static
class
CustomerSortingComparator
implements
Comparator<Student> {
@Override
public
int
compare(Student customer1,
Student customer2)
{
int
NameCompare = customer1.getName().compareTo(
customer2.getName());
int
AgeCompare = customer1.getAge().compareTo(
customer2.getAge());
return
(NameCompare ==
0
) ? AgeCompare
: NameCompare;
}
}
public
static
void
main(String[] args)
{
List<Student> al =
new
ArrayList<>();
Student obj1 =
new
Student(
"Ajay"
,
27
);
Student obj2 =
new
Student(
"Sneha"
,
23
);
Student obj3 =
new
Student(
"Simran"
,
37
);
Student obj4 =
new
Student(
"Ajay"
,
22
);
Student obj5 =
new
Student(
"Ajay"
,
29
);
Student obj6 =
new
Student(
"Sneha"
,
22
);
al.add(obj1);
al.add(obj2);
al.add(obj3);
al.add(obj4);
al.add(obj5);
al.add(obj6);
Iterator<Student> custIterator = al.iterator();
System.out.println(
"Before Sorting:n"
);
while
(custIterator.hasNext()) {
System.out.println(custIterator.next());
}
Collections.sort(al,
new
CustomerSortingComparator());
System.out.println(
"nnAfter Sorting:n"
);
for
(Student customer : al) {
System.out.println(customer);
}
}
}
Output
Before Sorting: Customer{Name=Ajay, Age=27} Customer{Name=Sneha, Age=23} Customer{Name=Simran, Age=37} Customer{Name=Ajay, Age=22} Customer{Name=Ajay, Age=29} Customer{Name=Sneha, Age=22} After Sorting: Customer{Name=Ajay, Age=22} Customer{Name=Ajay, Age=27} Customer{Name=Ajay, Age=29} Customer{Name=Simran, Age=37} Customer{Name=Sneha, Age=22} Customer{Name=Sneha, Age=23}
This article is contributed by Rishabh Mahrsee. If you like GeeksforGeeks and would like to contribute, you can also write an article and mail your article to review-team@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks. Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.