Предположим, у вас есть несколько объектов, у которых есть несколько полей, с которыми они могут сравниваться:
public class Person {
private String firstName;
private String lastName;
private String age;
/* Constructors */
/* Methods */
}
Так что в этом примере, когда вы спрашиваете:
a.compareTo(b) > 0
Вы могли бы спросить, если фамилия a предшествует b, или a старше b, и т. д ...
Каков самый чистый способ включить многократное сравнение между этими типами объектов, не добавляя ненужных помех и накладных расходов?
java.lang.Comparable
интерфейс позволяет сравнивать только по одному полю- Добавление многочисленные сравнения методов (то есть
compareByFirstName()
,compareByAge()
и т.д ...) загроможден на мой взгляд.
Так каков наилучший способ сделать это?
Ответы:
Вы можете реализовать объект,
Comparator
который сравнивает дваPerson
объекта, и вы можете исследовать столько полей, сколько хотите. Вы можете вставить переменную в ваш компаратор, которая скажет ему, с каким полем сравнивать, хотя, вероятно, было бы проще написать несколько компараторов.источник
С Java 8:
Если у вас есть методы доступа:
Если класс реализует Comparable, то такой метод сравнения может использоваться в методе CompareTo:
источник
(Person p)
важен для цепных компараторов.Comparator
экземпляры при каждом вызове?.thenComparing(Person::getLastName, Comparator.nullsFirst(Comparator.naturalOrder()))
- первый селектор полей, затем компараторcompareTo
как показано выше,Comparator
создается каждый раз, когда вызывается метод. Вы можете предотвратить это, сохранив компаратор в закрытом статическом окончательном поле.Вы должны реализовать
Comparable <Person>
. Предполагая, что все поля не будут нулевыми (для простоты), age - это int, а сравнение ранжирования - first, last, age,compareTo
метод довольно прост:источник
(из способов сортировки списков объектов в Java на основе нескольких полей )
Рабочий код в этой сути
Использование лямбды Java 8 (добавлено 10 апреля 2019 г.)
Java 8 хорошо решает эту проблему с помощью лямбды (хотя Guava и Apache Commons могут по-прежнему предлагать большую гибкость):
Благодаря ответу @ gaoagong ниже .
Грязный и запутанный: сортировка вручную
Это требует много печатания, обслуживания и подвержено ошибкам.
Отражающий способ: сортировка с помощью BeanComparator
Очевидно, что это более кратко, но еще более подвержено ошибкам, поскольку вы теряете прямую ссылку на поля, используя вместо этого строки (без безопасности типов, авторефакторинг). Теперь, если поле переименовано, компилятор даже не сообщит о проблеме. Более того, поскольку в этом решении используется отражение, сортировка выполняется намного медленнее.
Как добраться: сортировка с помощью Google Guava's ComparisonChain
Это намного лучше, но для некоторого случая использования требуется некоторый код пластины котла: по умолчанию нулевые значения должны оцениваться меньше. Для нулевых полей вы должны предоставить Guava дополнительную директиву, что делать в этом случае. Это гибкий механизм, если вы хотите сделать что-то конкретное, но часто вам нужен регистр по умолчанию (т. Е. 1, a, b, z, null).
Сортировка с помощью Apache Commons
Как и Guar's ComparisonChain, этот библиотечный класс легко сортируется по нескольким полям, но также определяет поведение по умолчанию для нулевых значений (т. Е. 1, a, b, z, null). Тем не менее, вы не можете указать что-либо еще, если вы не предоставите свой собственный компаратор.
таким образом
В конечном счете, все сводится к вкусу и необходимости гибкости (ComparisonChain от Guava) и лаконичного кода (CompareToBuilder от Apache).
Бонусный метод
Я нашел хорошее решение, которое объединяет несколько компараторов в порядке приоритета CodeReview в
MultiComparator
:У Ofcourse Apache Commons Collections есть утилита для этого:
ComparatorUtils.chainedComparator (comparatorCollection)
источник
@Patrick Для сортировки нескольких полей подряд попробуйте ComparatorChain
источник
Другой вариант, который вы всегда можете рассмотреть, - это Apache Commons. Это предоставляет много вариантов.
Пример:
источник
Вы также можете взглянуть на Enum, который реализует Comparator.
http://tobega.blogspot.com/2008/05/beautiful-enums.html
например
источник
источник
Для тех, кто в состоянии использовать потоковый API Java 8, есть более аккуратный подход, который хорошо документирован здесь: лямбды и сортировка
Я искал эквивалент C # LINQ:
Я нашел механизм в Java 8 на компараторе:
Итак, вот фрагмент, который демонстрирует алгоритм.
Посмотрите приведенную выше ссылку для более точного подхода и объяснения того, как вывод типа Java делает его более неуклюжим по сравнению с LINQ.
Вот полный тестовый модуль для справки:
источник
Написание
Comparator
вручную для такого варианта использования является ужасным решением IMO. Такие специальные подходы имеют много недостатков:Так в чем же решение?
Сначала немного теории.
Обозначим предложение «
A
сравнение опор типа » черезOrd A
. (С точки зрения программы вы можете думатьOrd A
как об объекте, содержащем логику для сравнения двухA
s. Да, точно так жеComparator
.)Теперь, если
Ord A
иOrd B
, то их состав(A, B)
также должен поддерживать сравнение. то естьOrd (A, B)
. ЕслиOrd A
,Ord B
иOrd C
, затемOrd (A, B, C)
.Мы можем расширить этот аргумент до произвольной арности и сказать:
Ord A, Ord B, Ord C, ..., Ord Z
⇒Ord (A, B, C, .., Z)
Давайте назовем это утверждение 1.
Сравнение композитов будет работать так же, как вы описали в своем вопросе: сначала будет выполнено первое сравнение, затем следующее, затем следующее и так далее.
Это первая часть нашего решения. Теперь вторая часть.
Если вы знаете , что
Ord A
, и знаете , как преобразоватьB
кA
(вызов этой функции преобразованияf
), то вы также можете иметьOrd B
. Как? Что ж, когдаB
нужно сравнить два экземпляра, вы сначала преобразовываете их вA
использование,f
а затем применяетеOrd A
.Здесь мы отображаем преобразование
B → A
вOrd A → Ord B
. Это называется контравариантным отображением (илиcomap
для краткости).Ord A, (B → A)
⇒ comapOrd B
Давайте назовем это утверждение 2.
Теперь давайте применим это к вашему примеру.
У вас есть названный тип данных,
Person
который состоит из трех полей типаString
.Мы знаем это
Ord String
. По утверждению 1Ord (String, String, String)
,.Мы можем легко написать функцию из
Person
в(String, String, String)
. (Просто верните три поля.) Поскольку мы знаемOrd (String, String, String)
иPerson → (String, String, String)
, используя утверждение 2, мы можем использовать,comap
чтобы получитьOrd Person
.QED.
Как мне реализовать все эти концепции?
Хорошей новостью является то, что вам не нужно. Уже существует библиотека, которая реализует все идеи, описанные в этом посте. (Если вам интересно, как они реализованы, вы можете заглянуть под капот .)
Вот как будет выглядеть код:
Объяснение:
stringOrd
это объект типаOrd<String>
. Это соответствует нашему первоначальному предложению «поддерживает сравнение».p3Ord
это метод , который принимаетOrd<A>
,Ord<B>
,Ord<C>
и возвращаетсяOrd<P3<A, B, C>>
. Это соответствует утверждению 1. (P3
означает продукт с тремя элементами. Продукт - это алгебраический термин для композитов.)comap
соответствует хорошоcomap
.F<A, B>
представляет функцию преобразованияA → B
.p
это фабричный метод для создания продуктов.Надеюсь, это поможет.
источник
Вместо методов сравнения вы можете просто определить несколько типов подклассов «Comparator» внутри класса Person. Таким образом, вы можете передать их в стандартные методы сортировки коллекций.
источник
Я думаю, что было бы более запутанным, если бы ваш алгоритм сравнения был «умным». Я бы пошел с многочисленными методами сравнения, которые вы предложили.
Единственным исключением для меня будет равенство. Для модульного тестирования мне было полезно переопределить .Equals (в .net), чтобы определить, равны ли несколько полей между двумя объектами (а не то, что ссылки равны).
источник
Если есть несколько способов, которыми пользователь может заказать человека, у вас также может быть несколько установок Comparator в качестве констант. Большинство операций сортировки и отсортированных коллекций принимают компаратор в качестве параметра.
источник
источник
Код реализации того же здесь, если мы должны отсортировать объект Person на основе нескольких полей.
источник
источник
Если вы реализуете интерфейс Comparable , вам нужно выбрать одно простое свойство для упорядочения. Это известно как естественный порядок. Думайте об этом как о умолчанию. Он всегда используется, когда нет конкретного компаратора. Обычно это имя, но ваш вариант использования может потребовать чего-то другого. Вы можете использовать любое количество других Компараторов, которые вы можете предоставить различным API коллекций, чтобы переопределить естественный порядок.
Также обратите внимание, что обычно, если a.compareTo (b) == 0, тогда a.equals (b) == true. Это нормально, если нет, но есть побочные эффекты, о которых нужно знать. Посмотрите превосходные javadocs на интерфейсе Comparable, и вы найдете много полезной информации об этом.
источник
В следующем блоге приведен пример цепного компаратора
http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example
Вызов компаратора:
источник
Начиная с ответа Стива, можно использовать троичный оператор:
источник
В java легко сравнивать два объекта методом хеш-кода
источник
Обычно я перезаписываю свой
compareTo()
метод таким образом, когда мне приходится выполнять многоуровневую сортировку.Здесь в первую очередь предпочтение отдается названию фильма, а затем исполнителю и, наконец, песне Longth. Вы просто должны убедиться, что эти множители достаточно далеко, чтобы не пересекать границы друг друга.
источник
Это легко сделать с помощью библиотеки Google Guava .
например
Objects.equal(name, name2) && Objects.equal(age, age2) && ...
Больше примеров:
источник