假设你有一些对象,它们有几个字段可以比较:

public class Person {

    private String firstName;
    private String lastName;
    private String age;

    /* Constructors */

    /* Methods */

}

所以在这个例子中,当你问if:

a.compareTo(b) > 0

你可能会问a的姓是不是在b的姓之前,或者a的年龄是不是比b大,等等……

在不增加不必要的混乱或开销的情况下,在这些类型的对象之间进行多重比较的最干净的方法是什么?

comparable接口只允许通过一个字段进行比较 在我看来,添加大量的比较方法(如compareByFirstName(), compareByAge()等)是混乱的。

那么最好的解决办法是什么呢?


当前回答

与比较方法不同,您可能只想在Person类中定义几种类型的“Comparator”子类。这样就可以将它们传递到标准的Collections排序方法中。

其他回答

对于那些能够使用Java 8流API的人来说,这里有一个更整洁的方法: lambda和排序

我正在寻找相当于c# LINQ:

.ThenBy(...)

我在Comparator上找到了Java 8的机制:

.thenComparing(...)

下面是演示算法的代码片段。

    Comparator<Person> comparator = Comparator.comparing(person -> person.name);
    comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));

请查看上面的链接,以获得更简洁的方法,并解释Java的类型推断如何使其与LINQ相比定义起来更笨拙。

下面是完整的单元测试供参考:

@Test
public void testChainedSorting()
{
    // Create the collection of people:
    ArrayList<Person> people = new ArrayList<>();
    people.add(new Person("Dan", 4));
    people.add(new Person("Andi", 2));
    people.add(new Person("Bob", 42));
    people.add(new Person("Debby", 3));
    people.add(new Person("Bob", 72));
    people.add(new Person("Barry", 20));
    people.add(new Person("Cathy", 40));
    people.add(new Person("Bob", 40));
    people.add(new Person("Barry", 50));

    // Define chained comparators:
    // Great article explaining this and how to make it even neater:
    // http://blog.jooq.org/2014/01/31/java-8-friday-goodies-lambdas-and-sorting/
    Comparator<Person> comparator = Comparator.comparing(person -> person.name);
    comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));

    // Sort the stream:
    Stream<Person> personStream = people.stream().sorted(comparator);

    // Make sure that the output is as expected:
    List<Person> sortedPeople = personStream.collect(Collectors.toList());
    Assert.assertEquals("Andi",  sortedPeople.get(0).name); Assert.assertEquals(2,  sortedPeople.get(0).age);
    Assert.assertEquals("Barry", sortedPeople.get(1).name); Assert.assertEquals(20, sortedPeople.get(1).age);
    Assert.assertEquals("Barry", sortedPeople.get(2).name); Assert.assertEquals(50, sortedPeople.get(2).age);
    Assert.assertEquals("Bob",   sortedPeople.get(3).name); Assert.assertEquals(40, sortedPeople.get(3).age);
    Assert.assertEquals("Bob",   sortedPeople.get(4).name); Assert.assertEquals(42, sortedPeople.get(4).age);
    Assert.assertEquals("Bob",   sortedPeople.get(5).name); Assert.assertEquals(72, sortedPeople.get(5).age);
    Assert.assertEquals("Cathy", sortedPeople.get(6).name); Assert.assertEquals(40, sortedPeople.get(6).age);
    Assert.assertEquals("Dan",   sortedPeople.get(7).name); Assert.assertEquals(4,  sortedPeople.get(7).age);
    Assert.assertEquals("Debby", sortedPeople.get(8).name); Assert.assertEquals(3,  sortedPeople.get(8).age);
    // Andi     : 2
    // Barry    : 20
    // Barry    : 50
    // Bob      : 40
    // Bob      : 42
    // Bob      : 72
    // Cathy    : 40
    // Dan      : 4
    // Debby    : 3
}

/**
 * A person in our system.
 */
public static class Person
{
    /**
     * Creates a new person.
     * @param name The name of the person.
     * @param age The age of the person.
     */
    public Person(String name, int age)
    {
        this.age = age;
        this.name = name;
    }

    /**
     * The name of the person.
     */
    public String name;

    /**
     * The age of the person.
     */
    public int age;

    @Override
    public String toString()
    {
        if (name == null) return super.toString();
        else return String.format("%s : %d", this.name, this.age);
    }
}
//Following is the example in jdk 1.8
package com;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class User {
    private String firstName;
    private String lastName;
    private Integer age;

    public Integer getAge() {
        return age;
    }

    public User setAge(Integer age) {
        this.age = age;
        return this;
    }

    public String getFirstName() {
        return firstName;
    }

    public User setFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    public String getLastName() {
        return lastName;
    }

    public User setLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

}

public class MultiFieldsComparision {

    public static void main(String[] args) {
        List<User> users = new ArrayList<User>();

        User u1 = new User().setFirstName("Pawan").setLastName("Singh").setAge(38);
        User u2 = new User().setFirstName("Pawan").setLastName("Payal").setAge(37);
        User u3 = new User().setFirstName("Anuj").setLastName("Kumar").setAge(60);
        User u4 = new User().setFirstName("Anuj").setLastName("Kumar").setAge(43);
        User u5 = new User().setFirstName("Pawan").setLastName("Chamoli").setAge(44);
        User u6 = new User().setFirstName("Pawan").setLastName("Singh").setAge(5);

        users.add(u1);
        users.add(u2);
        users.add(u3);
        users.add(u4);
        users.add(u5);
        users.add(u6);

        System.out.println("****** Before Sorting ******");

        users.forEach(user -> {
            System.out.println(user.getFirstName() + " , " + user.getLastName() + " , " + user.getAge());
        });

        System.out.println("****** Aftre Sorting ******");

        users.sort(
                Comparator.comparing(User::getFirstName).thenComparing(User::getLastName).thenComparing(User::getAge));

        users.forEach(user -> {
            System.out.println(user.getFirstName() + " , " + user.getLastName() + " , " + user.getAge());
        });

    }

}

如果用户有多种方式对人排序,你也可以在某个地方设置多个comparator常量。大多数排序操作和排序集合都以比较器作为参数。

您可以实现一个Comparator来比较两个Person对象,并且可以检查任意数量的字段。你可以在比较器中放入一个变量,告诉它与哪个字段进行比较,尽管只编写多个比较器可能会更简单。

下面的博客给出了一个很好的链式比较器的例子

http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * This is a chained comparator that is used to sort a list by multiple
 * attributes by chaining a sequence of comparators of individual fields
 * together.
 *
 */
public class EmployeeChainedComparator implements Comparator<Employee> {

    private List<Comparator<Employee>> listComparators;

    @SafeVarargs
    public EmployeeChainedComparator(Comparator<Employee>... comparators) {
        this.listComparators = Arrays.asList(comparators);
    }

    @Override
    public int compare(Employee emp1, Employee emp2) {
        for (Comparator<Employee> comparator : listComparators) {
            int result = comparator.compare(emp1, emp2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }
}

打电话比较器:

Collections.sort(listEmployees, new EmployeeChainedComparator(
                new EmployeeJobTitleComparator(),
                new EmployeeAgeComparator(),
                new EmployeeSalaryComparator())
        );