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

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()等)是混乱的。

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


当前回答

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

其他回答

从Steve的回答开始,三元操作符可以使用:

public int compareTo(Person other) {
    int f = firstName.compareTo(other.firstName);
    int l = lastName.compareTo(other.lastName);
    return f != 0 ? f : l != 0 ? l : Integer.compare(age, other.age);
}

在我看来,为这样的用例手动编写Comparator是一个糟糕的解决方案。这种特别的方法有很多缺点:

没有代码重用。违反了干。 样板。 增加了出错的可能性。


那么解决方案是什么呢?

首先是一些理论。

让我们用Ord A来表示命题“type A支持比较”(从程序的角度来看,您可以将Ord A看作一个包含比较两个A的逻辑的对象。是的,就像Comparator一样。)

现在,如果Ord A和Ord B,那么它们的合成(A, B)也应该支持比较。例如,Ord (A, B)。如果Ord A, Ord B,和Ord C,那么Ord (A, B, C)。

我们可以将这个论证扩展到任意性,并说:

A站,B站,C站,……, Ord Z⇒Ord (A, B, C, .., Z)

我们称这个为表述一。

复合材料的比较将像您在问题中描述的那样工作:首先尝试第一个比较,然后是下一个比较,然后是下一个比较,依此类推。

这是解的第一部分。现在是第二部分。

如果你知道A字词,也知道如何将B转换为A(称之为转换函数f),那么你也可以有B字词,怎么做?当要比较两个B实例时,首先使用f将它们转换为A,然后应用Ord A。

在这里,我们将变换B→A映射到Ord A→Ord B,这被称为逆变映射(或简称comap)。

Ord A, (B→A)

我们称这个为表述二。


现在让我们把这个应用到你的例子中。

您有一个名为Person的数据类型,它包含三个String类型的字段。

我们知道Ord String。通过语句1,Ord(字符串,字符串,字符串)。 我们可以很容易地编写一个从Person到(String, String, String)的函数。(只返回三个字段。)由于我们知道Ord (String, String, String)和Person→(String, String, String),通过语句2,我们可以使用comap来获得Ord Person。

QED.


我如何实现所有这些概念?

好消息是你不必这么做。已经有一个库实现了这篇文章中描述的所有想法。(如果你对这些是如何实现的感到好奇,你可以深入了解一下。)

下面是代码的外观:

Ord<Person> personOrd = 
 p3Ord(stringOrd, stringOrd, stringOrd).comap(
   new F<Person, P3<String, String, String>>() {
     public P3<String, String, String> f(Person x) {
       return p(x.getFirstName(), x.getLastname(), x.getAge());
     }
   }
 );

解释:

stringOrd是Ord<String>类型的对象。这与我们最初的“支持比较”命题相对应。 p3Ord是一个获取Ord< a >, Ord<B>, Ord<C>,并返回Ord<P3< a, B, C>>的方法。这对应于表述一。(P3代表三要素产品。Product是复合材料的代数术语。) Comap对应于Comap。 F<A, B>表示变换函数A→B。 P是创建产品的工厂方法。 整个表达式对应于表述2。

希望这能有所帮助。

如果我们必须基于多个字段对Person对象进行排序,则相同的代码实现在这里。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Person {

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

public String getFirstName() {
    return firstName;
}

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

public String getLastName() {
    return lastName;
}

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

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

public Person(String firstName, String lastName, int age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
}


static class PersonSortingComparator implements Comparator<Person> {

    @Override
    public int compare(Person person1, Person person2) {

        // for first name comparison
        int firstNameCompare = person1.getFirstName().compareTo(person2.getFirstName());

        // for last name comparison
        int lastNameCompare = person1.getLastName().compareTo(person2.getLastName());

        // for last name comparison
        int ageCompare = person1.getAge() - person2.getAge();

        // Now comparing
        if (firstNameCompare == 0) {
            if (lastNameCompare == 0) {
                return ageCompare;
            }
            return lastNameCompare;
        }
        return firstNameCompare;
    }
}

public static void main(String[] args) {
    Person person1 = new Person("Ajay", "Kumar", 27);
    Person person2 = new Person("Ajay","Gupta", 23);
    Person person3 = new Person("Ajay","Kumar", 22);


    ArrayList<Person> persons = new ArrayList<>();
    persons.add(person1);
    persons.add(person2);
    persons.add(person3);


    System.out.println("Before Sorting:\n");
    for (Person person : persons) {
        System.out.println(person.firstName + " " + person.lastName + " " + person.age);
    }

    Collections.sort(persons, new PersonSortingComparator());

    System.out.println("After Sorting:\n");
    for (Person person : persons) {
        System.out.println(person.firstName + " " + person.lastName + " " + person.age);
    }
}

}

要连续排序多个字段,请尝试ComparatorChain

A ComparatorChain is a Comparator that wraps one or more Comparators in sequence. The ComparatorChain calls each Comparator in sequence until either 1) any single Comparator returns a non-zero result (and that result is then returned), or 2) the ComparatorChain is exhausted (and zero is returned). This type of sorting is very similar to multi-column sorting in SQL, and this class allows Java classes to emulate that kind of behaviour when sorting a List. To further facilitate SQL-like sorting, the order of any single Comparator in the list can >be reversed. Calling a method that adds new Comparators or changes the ascend/descend sort after compare(Object, Object) has been called will result in an UnsupportedOperationException. However, take care to not alter the underlying List of Comparators or the BitSet that defines the sort order. Instances of ComparatorChain are not synchronized. The class is not thread-safe at construction time, but it is thread-safe to perform multiple comparisons after all the setup operations are complete.

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