Поиск…


Синтаксис

  • Открытый класс 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...
        }
    });
}
Java SE 8

Комбинированные на основе 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 позволяющие сортировать итерации карт.

Java SE 8
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:

Java SE 8
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)

Это создаст компаратор, который сравнивает с фамилией, затем сравнивает его с именем. Вы можете объединить столько компараторов, сколько захотите.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow