Bazaprogram.ru

Новости из мира ПК
2 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Java util comparator

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

Результат выполнения программы:

В примере значения сортируются сначала по полю str (по алфавиту), а затем по num в методе compareTo. Это хорошо видно по двум строкам с одинаковыми значения str и различными num. Чтобы изменить порядок сортировки значения str (в обратном порядке), необходимо внести небольшие изменения в метод compareTo.

Интерфейс Comparator : compare, compareTo

В интерфейсе Comparator объявлен метод compare (Object obj1, Object obj2), который позволяет сравнивать между собой два объекта. На выходе метод возвращает значение 0, если объекты равны, положительное значение или отрицательное значение, если объекты не тождественны.

Метод может вызвать исключение ClassCastException, если типы объектов не совместимы при сравнении. Простой пример реализации интерфейса Comparator:

Результат выполнения программы:

Усложним пример, и реализуем несколько видов сортировки. Для этого создадим класс Product с полями name, price и quantity.

Создадим два класса (SortedByName, SortedByPrice), реализующих интерфейс Comparator для сортировки объектов по названию и по цене :

Пример использования Arrays.sort :

Результат выполнения программы:

Для сортировки объектов были реализованы два независимых компаратора по наименованию и по цене (SortedByName и SortedByPrice). Сортировка выполняется с помощью класса Arrays, у которого есть метод sort. Данный метод в качестве второго аргумента принимает тип компаратора.

Можно использовать также метод sort класса Collections, который в качестве первого входного аргумента принимает список объектов:

Отличие интерфейсов Comparator и Comparable

Интерфейс Comparable используется только для сравнения объектов класса, в котором данный интерфейс реализован. Т.е. interface Comparable определяет логику сравнения объекта определенного ссылочного типа внутри своей реализации (по правилам разработчика).

Comparator представляет отдельную реализацию и ее можно использовать многократно и с различными классами. Т.е. interface Comparator позволяет создавать объекты, которые будут управлять процессом сравнения (например при сортировках).

Пишите компараторы правильно

В Java для введения порядка среди определённых объектов можно написать компаратор — класс, содержащий функцию compare , которая сравнивает два объекта. Альтернативой компаратору является естественный порядок объектов: объект реализует интерфейс Comparable , который содержит метод compareTo , позволяющий сравнить этот объект с другим. Сравнивающая функция должна вернуть 0, если объекты равны, отрицательное число (обычно -1), если первый объект меньше второго, и положительное число (обычно 1), если первый больше. Обычно реализация такой функции не представляет сложностей, но имеется один случай, о котором многие забывают.

Сравнение используется различными алгоритмами от сортировки и двоичного поиска до поддержания порядка в сортированных коллекциях вроде TreeMap . Эти алгоритмы завязаны на три важных свойства сравнивающей функции: рефлексивность (сравнение элемента с самим собой всегда даёт 0), антисимметричность (сравнение A с B и B с A должны дать разный знак) и транзитивность (если сравнение A с B и B с C выдаёт одинаковый знак, то и сравнение A с C должно выдать такой же). Если сравнивающая функция не удовлетворяет этим свойствам, алгоритм может выдать совершенно непредсказуемый результат. Причём скорее всего вы не получите никакого исключения, просто результат будет неверный.

Как обнаружилось, несоблюдение этих свойств — не такая уж редкая ситуация. Проблема возникает при сравнении вещественных чисел — float или double.

Предположим, у нас имеется класс с полем типа double, и мы хотим упорядочивать объекты этого класса в зависимости от значения поля. Нередко можно встретить такой код:

У приведённого метода compareTo есть две проблемы. Первая — незначительная — он не различает +0.0 и -0.0: new DoubleHolder(-0.0).compareTo(new DoubleHolder(+0.0)) вернёт 0. Иногда это нестрашно, но в случае сортировки элементы с +0.0 и -0.0 расположатся в произвольном порядке, что будет смотреться некрасиво. Тем не менее, всё это мелочи по сравнению с NaN. Число NaN (как в типе double, так и во float) — это довольно специфичная вещь. Оно не больше, не меньше и не равно никакому другому числу. В результате мы сразу получаем нарушение свойств сравнения:

Ни рефлексивности, ни антисимметричности не наблюдается. Можно встретить и такую реализацию сравнения:

Здесь все три предыдущих сравнения выдадут ноль, то есть как будто бы свойства соблюдаются. Но, конечно, радоваться рано:

Читать еще:  Java lang throwable

Здесь нарушается транзитивность: первый объект равен второму, второй равен третьему, но первый третьему не равен.

Чем же это грозит простому обывателю? Чтобы понять это, создадим простой список и попробуем его перемешать и посортировать несколько раз:

Вывод в каждом запуске отличается и может выглядеть, например, так:

Или для второй реализации compareTo :

Примерно в половине случаев элемент с NaN прибивается к началу или концу сортируемого списка, а в других случаях начинает блуждать, портя порядок окружающих элементов.

С коллекциями, использующими DoubleHolder в качестве ключа, тоже ничего хорошего не произойдёт. Возьмём, к примеру, TreeSet . Со второй реализацией compareTo всё довольно просто: так как элемент, содержащий NaN, равен любому другому, то в непустое множество вставить его не получится, потому что оно решит, что такой элемент уже есть. Но берегитесь, если вы вставили NaN-элемент первым: после этого во множество не выйдет добавить ничего другого.

Первый вариант сравнивающей функции психоделичнее. Напишем, например, такой тест:

Мы вставили по пять элементов, содержащих NaN, и по пять элементов, содержащих каждую цифру от 1 до 9. В результате имеем следующее:

Вполне ожидаемо увидеть пять раз NaN: ведь они не равны друг другу. Но из-за неправильных сравнений и некоторые другие элементы вставились по нескольку раз. Можете сами посмотреть, что случится с TreeMap. Заметьте, что один случайно попавший NaN может испортить всю коллекцию, причём это не всегда легко отладить: коллекция может долго существовать в некорректном состоянии и делать вид, что всё нормально.

Что любопытно, этой проблемы вообще не должно существовать. Ещё в JDK 1.4 появились специальные статические методы Float.compare и Double.compare, которые сделают всё за вас, корректно обработав специальные случаи. Надо лишь написать:

Видимо, сказывается обманчивая простота алгоритма сравнения: порой программист считает, что проще написать самому, чем искать в документации стандартный способ сделать это.

Примеры неправильного сравнения double/float в различных открытых проектах: JTS, Batik, Hadoop, Hudson, ICU4J, Lucene. Трудно определить, в каких случаях это может привести к проблемам, но это тот случай, когда я бы исправлял безусловно: правильный и надёжный вариант обычно при этом ещё и короче неправильного.

Чтобы изменить ситуацию, я написал маленький детектор для FindBugs, который находит некорректно реализованные функции сравнения и предлагает использовать Float.compare/Double.compare.

Вообще для всех примитивных типов есть подобные методы. Если вам надо сравнить несколько полей по очереди, можно написать так:

Смотрится лучше, чем куча веток с больше и меньше. А если вы пользуетесь Guava или чем-то подобным, тогда так:

Интерфейсы Comparable и Comparator в Java

Привет! Это статья про интерфейсы Comparable и Comparator. Для понимания материала Вам, естественно, нужно знать что такое интерфейс.

Что такое Comparable и Comparator в Java

В своей работе программисты часто сталкиваются с тем, что надо что-то сортировать — например, покупая товары в интернет-магазине Вы можете сортировать их по цене, популярности и т.д.

Но для того, чтобы что-то отсортировать, нам нужно сравнивать объекты по каким-то правилам. Тут, казалось бы, все просто — мы можем сортировать числа, да и в сортировке по алфавиту нет ничего сложного. Да, с такими данными все легко. Но как нам сравнить два объекта класса Car? По цене, пробегу, лошадиным силам или дате выпуска? А может по количеству владельцев?

Если у нас есть два объекта класса Cat — как сравнить их? По кличке? По породе? По возрасту?

Как видите, не всегда очевидно как именно можно сравнить два объекта. Но не беда — мы сами можем прописать эти правила. Именно для этого мы можем реализовать интерфейсы Comparable и Comparator.

  • Кроме того, некоторые встроенные возможности в Java можно использовать только, если Ваш класс реализует Comparable или Comparator.

Интерфейс Comparable

С английского «Comparable» переводится как «сравнимый». Имплементируя этот интерфейс мы как бы говорим «Эй, теперь объекты этого класса можно сравнивать между собой! И я знаю, как это сделать!» А до этого было нельзя

Так как выглядит интерфейс Comparable? Очень просто — в нем находится всего один метод:

Do You Know – Comparator in Java 7 had just 2 methods – compare() and equals() . The enhanced Comparator in Java 8 now boasts of 19 methods. Yes, 17 more methods! What’s more Comparator now is an official Functional Interface as well!

Comparator has undergone a major overhaul in Java 8 while still retaining its essence which is to compare and sort objects in Collections. Comparator now supports declarations via lambda expressions Read Lambda Expressions Tutorial as it is a Functional Interface. Comparator has a new method comparing() which uses an instance of java.util.function.Function functional interface, specified using lambda expression or its equivalent method reference, for Comparator instance creation. In addition, multiple sort criteria can now be clubbed using comparing() with a thenComparing() method. The range of new capabilities is rounded off with methods for using natural comparison order, in-built Null handling and sort order reversal.

Читать еще:  Java linkedlist пример

In this tutorial, we will first take a quick look at how Comparators were used before Java 8. We will then take an in-depth look at the new Comparator aspects mentioned above to see how java.util.Comparator has evolved into an enhanced comparison and ordering utility in Java 8.

How Comparator was used prior to Java 8
Until Java 7, Comparator interface could only be used in one single way. Given a collection of objects of type to sort, one would create an implementation of Comparator interface, overr >compare() method of the interface with the desired comparison logic and use Collections.sort() or similar such methods in Collections API to sort the collection of objects.

Let us now see an example of how Comparators were used prior to Java 8. Let us first create a >Employee which will be the type of object we will be sorting across all our examples for Java 7 and Java 8 Comparators-

Next we will see how Comparator was used to sort collections prior to Java 8 –

  • EmployeeComparator class implements Comparator inteface and overrides the compare() method to compare Employee objects passed to it based on the natural ordering of their names of String type.
  • ComparatorOldWay class sorts a static list of Employee objects using an instance of EmployeeComparator and the Collections.sort() method.
  • Output shows that the employeeList gets sorted alphabetically based on the names of the employees.

Java 8’s Comparator is a Functional Interface
Owing to the fact that the Comparator interface has just one abstract method, compare() , it automatically qualifies to be a Functional Interface
Click to read detailed Article on Functional Interfaces in Java 8. Nevertheless, Java 8 designers have gone ahead and annotated the Comparator >@FunctionalInterface to enforce its role as a Functional Interface. Being a functional interface, Comparator can now be used as an assignment target for a lambda expression or a method reference.

Java 8’s Comparator as an assignment target for LambdaExpressions
Given the fact that its a functional interface, an instance of a Comparator can now be created in Java 8 with a lambda expression specifying its comparison logic. Take a look at the code snippet below –

  • ComparatorsInJava8 class uses the same list of Employee objects as that used for the previous example for Java 7 style of Comparator .
  • An instance of the Comparator , empNameComparator , is created using a lambda expression.
  • The lambda expression takes 2 Employee instances, emp1 and emp2 , as input and outputs the comparison of their names using the natural comparison order of Strings.
  • Using empNameComparator for sorting results in a correctly sorted Employee list by name.

Java 8 Comparator’s comparing() method’s working
The comparing() method is a new static method introduced in Comparators in Java 8. It has the signature –

comparing() method works by taking a Function functional interface instance as an input, where T is the type of input object and R is the sort key which is returned (or extracted) from the input object when Function processes it.

Let’s see a code snippet to understand the use of comparing() method. I have removed the code which was same as previous example below for brevity –

  • An instance of the Comparator , comparatorObj , is created using the static method Comparator.comparing() .
  • The comparing() is passed a lambda expression, which corresponds to a Function instance accepting an Employee object as input and returns an employee name – the sort key.
  • Using comparatorObj for sorting results in a correctly sorted Employee list by name.
  • NOTE – Instead of the lambda expression, you can also use an equivalent method reference as well. The comparing() method with a method reference will then be written like this –

Java 8 Comparator’s thenComparing() method for multiple sort criteria
Many-a-times we need to sort with multiple sort orders. I.e. on more than one attributes of an object. The second level sort order gets used if the first level sort criteria is indecisive. In the list of Employees we are using as an example, there are two employees with the name Harry Major. Let us take a second sort order of age and say that in such cases we will put the employee with the younger age first.

For exactly these kinds of multiple sort ordering, Java 8 Comparator prov >thenComparing() which has the signature –

Читать еще:  Unexpected identifier javascript

The thenComparing() method then does the second level sort, if the first level sort is indecisive. Let us extend the above code example to add a second-level sort criteria by age.

  • First comparing() method is invoked with method reference for Employee ‘s getName() method. This returns a Comparator instance with the first level sort based on Employee name as we saw in previous section.
  • We append .thenComparing(Employee::getAge) to the Comparator instance returned using comparing() method, which adds a second level sort based on Employee ‘s getAge() method.
  • The output is as expected with the employee named ‘Harry Major’ with the lesser age placed earlier in the sorted employeeList than his elder namesake employee.

Java 8 Comparator’s natural order comparison methods
Java 8 Comparator supports natural order comparison of elements in a Collection. I.e. instead of defining our own comparison logic, we can instead use the inherent natural order defined for that type of element via its implementation of Comparable interface. Comparator prov >naturalOrder() and reverseOrder() to allow natural order comparison and reverse natural order comparison respectively. These have the syntax –

Let us take the case of String type which has natural comparison order defined as alphabetical. To use our existing example for sorting using Employee objects, we will extract out all the names of Employees by utilizing the stream() method to convert the Employee list into a Stream of Employee objects. We will then map these Employee objects using a Function into their names and collect these names into a list of Strings holding these names.
(In case you are not aware of mapping of elements in a Stream using map() method you can read the tutorial on Stream.map() method
Tutorial on how Stream API’s map() method works , and tutorial on Function interface
Read java.util.function.Function interface tutorial .)

  • Employee names are extracted into an empNames List as explained before the code snippet.
  • empNames List is then sorted using the Comparator.naturalOrder() method which returns a Comparator instance of String ’s natural comparison order based on the empName ’s generic type of String .
  • Employee names are sorted in natural comparison order, i.e. alphabetical order, and printed.
  • Instead of invoking the Comparator.naturalOrder() method to the empNames.sort() method, you can invoke the Comparator.reverseOrder() method in order to sort the empNames List in reverse of natural comparison order or reverse alphabetical order.

Java 8 Comparator’s null handling using nullsFirst()and nullsLast() methods
There are instances where the sort key value is nullable. We have to then dec >nullsFirst() and nullsLast() static methods for exactly such sort order handling of null valued sort keys. These methods have the signature –

To understand the functioning of these methods, let us make the two of the employee names as null. The below two sections of code and respective output shows how the use of nullsFirst() and nullsLast() handles sorting when some objects have sort-key as null.

  • Using nullsFirst() , the comparator places the two Employee objects with the sort key null (employee name null) before the other Employee objects in the list.
  • Likewise, nullsLast() , the comparator places these two Employee objects after the other Employee objects in the list.

Java 8 Comparator’s sort order reversal method – reversed()
In case you simply want to sort in the order opposite to a defined comparator’s sorting order then you need not write the reverse comparison logic again. Instead simply use the Comparator.reversed() default method.

The reversed() method has the signature –

  • Comparator’s default method reversed() is applied to the empNameComparator which has been initially defined to sort in alphabetical order of Employee names.
  • Due to reversed() being applied to it, the Employee objects are printed in reverse alphabetical order of their names.

Summary – In this tutorial on Java 8 Comparators we first had a look at how Comparators were used till Java 7. Next we learnt that Comparator is now a functional interface and how it can be defined using a lambda expression. We then had a look at the comparing() method which allows us to define a Comparator using a Function instance which returns the sort key. Next we looked at thenComparing() method which allows us to sort using multiple sort orders. This was followed by understanding the natural order comparison related methods of Comparator – naturalOrder() and reverseOrder() . Next we looked at the null-valued sort key handling methods – nullsFirst() and nullsLast() . Lastly, we understood the reversed() method which allows us to sort in the reverse order of a defined Comparator .

Ссылка на основную публикацию
Adblock
detector