Java Language
Сравнительный и компаратор
Поиск…
Синтаксис
- Открытый класс MyClass реализует Comparable
<MyClass
> - Открытый класс MyComparator реализует Comparator
<SomeOtherClass
> - public int compareTo (другие MyClass)
- public int compare (SomeOtherClass o1, SomeOtherClass o2)
замечания
При реализации метода compareTo(..)
который зависит от double
, не выполняйте следующее:
public int comareTo(MyClass other) {
return (int)(doubleField - other.doubleField); //THIS IS BAD
}
Усечение, вызванное действием (int)
приведет к тому, что метод иногда неверно возвращает 0
вместо положительного или отрицательного числа и может таким образом привести к ошибкам сравнения и сортировки.
Вместо этого простейшей правильной реализацией является использование Double.compare , как такового:
public int comareTo(MyClass other) {
return Double.compare(doubleField,other.doubleField); //THIS IS GOOD
}
Неоднородная версия Comparable<T>
, просто Comparable
, существовала со времен Java 1.2 . Помимо взаимодействия с устаревшим кодом, всегда лучше реализовать общую версию Comparable<T>
, так как она не требует кастинга при сравнении.
Для класса очень стандартно сопоставимо с самим собой, как в:
public class A implements Comparable<A>
Хотя можно отказаться от этой парадигмы, будьте осторожны при этом.
Comparator<T>
все еще может использоваться для экземпляров класса, если этот класс реализует Comparable<T>
. В этом случае будет использоваться логика Comparator
; естественный порядок, указанный в реализации Comparable
будет проигнорирован.
Сортировка списка с использованием Comparable или компаратором
Предположим, что мы работаем над классом, представляющим Личность, по имени и фамилии. Мы создали базовый класс для этого и реализовали правильные методы equals
и hashCode
.
public class Person {
private final String lastName; //invariant - nonnull
private final String firstName; //invariant - nonnull
public Person(String firstName, String lastName){
this.firstName = firstName != null ? firstName : "";
this.lastName = lastName != null ? lastName : "";
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String toString() {
return lastName + ", " + firstName;
}
@Override
public boolean equals(Object o) {
if (! (o instanceof Person)) return false;
Person p = (Person)o;
return firstName.equals(p.firstName) && lastName.equals(p.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}
}
Теперь мы хотели бы отсортировать список объектов Person
по их имени, например, в следующем сценарии:
public static void main(String[] args) {
List<Person> people = Arrays.asList(new Person("John", "Doe"),
new Person("Bob", "Dole"),
new Person("Ronald", "McDonald"),
new Person("Alice", "McDonald"),
new Person("Jill", "Doe"));
Collections.sort(people); //This currently won't work.
}
К сожалению, как отмечено, вышесказанное в настоящее время не будет компилироваться. Collections.sort(..)
знает только, как сортировать список, если элементы в этом списке сопоставимы или задан пользовательский метод сравнения.
Если вас попросили отсортировать следующий список: 1,3,5,4,2
, у вас не возникло бы проблемы с ответом 1,2,3,4,5
. Это связано с тем, что целые (как в Java, так и математически) имеют естественное упорядочение , стандартное стандартное сравнение сравнения по умолчанию. Чтобы дать нашему классу Person естественный порядок, мы реализуем Comparable<Person>
, который требует реализации метода compareTo(Person p):
public class Person implements Comparable<Person> {
private final String lastName; //invariant - nonnull
private final String firstName; //invariant - nonnull
public Person(String firstName, String lastName) {
this.firstName = firstName != null ? firstName : "";
this.lastName = lastName != null ? lastName : "";
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String toString() {
return lastName + ", " + firstName;
}
@Override
public boolean equals(Object o) {
if (! (o instanceof Person)) return false;
Person p = (Person)o;
return firstName.equals(p.firstName) && lastName.equals(p.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}
@Override
public int compareTo(Person other) {
// If this' lastName and other's lastName are not comparably equivalent,
// Compare this to other by comparing their last names.
// Otherwise, compare this to other by comparing their first names
int lastNameCompare = lastName.compareTo(other.lastName);
if (lastNameCompare != 0) {
return lastNameCompare;
} else {
return firstName.compareTo(other.firstName);
}
}
}
Теперь основной метод будет функционировать правильно
public static void main(String[] args) {
List<Person> people = Arrays.asList(new Person("John", "Doe"),
new Person("Bob", "Dole"),
new Person("Ronald", "McDonald"),
new Person("Alice", "McDonald"),
new Person("Jill", "Doe"));
Collections.sort(people); //Now functions correctly
//people is now sorted by last name, then first name:
// --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald
}
Если, однако, вы либо не хотите, либо не можете изменить класс Person
, вы можете предоставить пользовательский Comparator<T>
который обрабатывает сравнение любых двух объектов Person
. Если вас попросили отсортировать следующий список: circle, square, rectangle, triangle, hexagon
вы не могли бы, но если бы вас попросили отсортировать этот список по количеству углов , вы могли бы. Именно поэтому предоставление компаратора инструктирует Java, как сравнивать два нормально не сопоставимых объекта.
public class PersonComparator implements Comparator<Person> {
public int compare(Person p1, Person p2) {
// If p1's lastName and p2's lastName are not comparably equivalent,
// Compare p1 to p2 by comparing their last names.
// Otherwise, compare p1 to p2 by comparing their first names
if (p1.getLastName().compareTo(p2.getLastName()) != 0) {
return p1.getLastName().compareTo(p2.getLastName());
} else {
return p1.getFirstName().compareTo(p2.getFirstName());
}
}
}
//Assume the first version of Person (that does not implement Comparable) is used here
public static void main(String[] args) {
List<Person> people = Arrays.asList(new Person("John", "Doe"),
new Person("Bob", "Dole"),
new Person("Ronald", "McDonald"),
new Person("Alice", "McDonald"),
new Person("Jill", "Doe"));
Collections.sort(people); //Illegal, Person doesn't implement Comparable.
Collections.sort(people, new PersonComparator()); //Legal
//people is now sorted by last name, then first name:
// --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald
}
Компараторы также могут быть созданы / использованы как анонимный внутренний класс
//Assume the first version of Person (that does not implement Comparable) is used here
public static void main(String[] args) {
List<Person> people = Arrays.asList(new Person("John", "Doe"),
new Person("Bob", "Dole"),
new Person("Ronald", "McDonald"),
new Person("Alice", "McDonald"),
new Person("Jill", "Doe"));
Collections.sort(people); //Illegal, Person doesn't implement Comparable.
Collections.sort(people, new PersonComparator()); //Legal
//people is now sorted by last name, then first name:
// --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald
//Anonymous Class
Collections.sort(people, new Comparator<Person>() { //Legal
public int compare(Person p1, Person p2) {
//Method code...
}
});
}
Комбинированные на основе Lambda выражения
Начиная с Java 8, компараторы также могут быть выражены в виде лямбда-выражений
//Lambda
Collections.sort(people, (p1, p2) -> { //Legal
//Method code....
});
Методы сравнения по умолчанию
Кроме того, существуют интересные методы по умолчанию для интерфейса компаратора для построения компараторов: следующее построение сравнительного сравнения по lastName
а затем firstName
.
Collections.sort(people, Comparator.comparing(Person::getLastName)
.thenComparing(Person::getFirstName));
Обращаясь к порядку компаратора
Любой компаратор также может быть легко изменен с помощью метода reversedMethod
который изменит восходящий порядок на нисходящий.
Сравнение и сравнение методов
Интерфейс Comparable<T>
требует одного метода:
public interface Comparable<T> {
public int compareTo(T other);
}
И интерфейс Comparator<T>
требует одного метода:
public interface Comparator<T> {
public int compare(T t1, T t2);
}
Эти два метода делают одно и то же, с одной незначительной разницей: compareTo
сравнивает this
с other
, тогда как compare
сравнивает t1
с t2
, не заботясь об this
.
Помимо этой разницы, оба метода имеют схожие требования. В частности (для сравнения), сравнивает этот объект с указанным объектом для заказа. Возвращает отрицательное целое число, ноль или положительное целое число, так как этот объект меньше, равен или больше указанного объекта. Таким образом, для сравнения a
и b
:
- Если
a < b
,a.compareTo(b)
иcompare(a,b)
должны возвращать отрицательное целое число, аb.compareTo(a)
иcompare(b,a)
следует возвращать положительное целое число - Если
a > b
,a.compareTo(b)
иcompare(a,b)
должны возвращать положительное целое число, аb.compareTo(a)
иcompare(b,a)
следует возвращать отрицательное целое число - Если
a
равноb
для сравнения, все сравнения должны возвращать0
.
Естественная (сопоставимая) и явная (сравнительная) сортировка
Существует два метода Collections.sort()
:
- Один, который принимает
List<T>
в качестве параметра, гдеT
должен реализовать Comparable и переопределить методcompareTo()
который определяет порядок сортировки. - Один, который принимает список и компаратор в качестве аргументов, где компаратор определяет порядок сортировки.
Во-первых, вот класс Person, который реализует Comparable:
public class Person implements Comparable<Person> {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Person o) {
return this.getAge() - o.getAge();
}
@Override
public String toString() {
return this.getAge()+"-"+this.getName();
}
}
Вот как вы могли бы использовать вышеприведенный класс для сортировки списка в естественном порядке его элементов, определяемого переопределением метода compareTo()
:
//-- usage
List<Person> pList = new ArrayList<Person>();
Person p = new Person();
p.setName("A");
p.setAge(10);
pList.add(p);
p = new Person();
p.setName("Z");
p.setAge(20);
pList.add(p);
p = new Person();
p.setName("D");
p.setAge(30);
pList.add(p);
//-- natural sorting i.e comes with object implementation, by age
Collections.sort(pList);
System.out.println(pList);
Вот как вы можете использовать анонимный встроенный компаратор для сортировки списка, который не реализует Comparable, или в этом случае, для сортировки списка в порядке, отличном от естественного упорядочения:
//-- explicit sorting, define sort on another property here goes with name
Collections.sort(pList, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getName().compareTo(o2.getName());
}
});
System.out.println(pList);
Сортировка записей в карте
Начиная с Java 8 на интерфейсе Map.Entry
существуют методы по Map.Entry
позволяющие сортировать итерации карт.
Map<String, Integer> numberOfEmployees = new HashMap<>();
numberOfEmployees.put("executives", 10);
numberOfEmployees.put("human ressources", 32);
numberOfEmployees.put("accounting", 12);
numberOfEmployees.put("IT", 100);
// Output the smallest departement in terms of number of employees
numberOfEmployees.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.limit(1)
.forEach(System.out::println); // outputs : executives=10
Конечно, они также могут использоваться вне потока api:
List<Map.Entry<String, Integer>> entries = new ArrayList<>(numberOfEmployees.entrySet());
Collections.sort(entries, Map.Entry.comparingByValue());
Создание компаратора с использованием метода сравнения
Comparator.comparing(Person::getName)
Это создает компаратор для класса Person
который использует имя этого человека в качестве источника сравнения. Также можно использовать версию метода для сравнения long, int и double. Например:
Comparator.comparingInt(Person::getAge)
Обратный порядок
Чтобы создать компаратор, который накладывает обратный порядок, используйте метод reverse reversed()
:
Comparator.comparing(Person::getName).reversed()
Цепь компараторов
Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName)
Это создаст компаратор, который сравнивает с фамилией, затем сравнивает его с именем. Вы можете объединить столько компараторов, сколько захотите.