55

我有一组 Duck 对象,我想使用多个键对它们进行排序

class Duck {
    DuckAge age; //implements Comparable
    DuckWeight weight; //implements Comparable
    String name;
}
List<Duck> ducks = Pond.getDucks();

例如。我想主要按体重排序,其次按年龄排序。如果两只鸭子的重量和年龄完全相同,那么让我们用它们的名字作为第三键来区分它们。我可能会做这样的事情:

Collections.sort(ducks, new Comparator<Duck>(){
    @Override
    public int compare(Duck d1, Duck d2){
        int weightCmp = d1.weight.compareTo(d2.weight);
        if (weightCmp != 0) {
            return weightCmp;
        }
        int ageCmp = d1.age.compareTo(d2.age);
        if (ageCmp != 0) {
            return ageCmp;
        }
        return d1.name.compareTo(d2.name);
    }
});

好吧,我经常这样做,但是这个解决方案闻起来不太对。它不能很好地扩展,而且很容易搞砸。肯定有更好的方法来使用多个键对 Ducks 进行排序!有人知道更好的解决方案吗?

编辑删除了不必要的else分支

4

7 回答 7

52

番石榴更优雅:

return ComparisonChain.start()
     .compare(d1.weight, d2.weight)
     .compare(d1.age, d2.age)
     .compare(d1.name, d2.name)
     .result();

Apache commons-lang有一个类似的结构,CompareToBuilder.

于 2011-11-07T12:29:01.947 回答
21
List<Duck> ducks = new ArrayList<Duck>();
Collections.sort(ducks, new Comparator<Duck>() {

  @Override
  public int compare(Duck o1, Duck o2) {

    return new org.apache.commons.lang.builder.CompareToBuilder().
        append(o1.weight, o2.weight).
        append(o1.age, o2.age).
        append(o1.name, o2.name).
        toComparison();
  }
});
于 2011-11-07T12:30:46.780 回答
20

Java 8 解决方案:

Comparator<Duck> cmp = Comparator.comparing(Duck::getWeight)
    .thenComparing(Duck::getAge)
    .thenComparing(Duck::getName);

万岁 lambdas、方法引用和默认方法:)!太糟糕了,我们必须定义 getter,或者使用显式 lambdas,如下所示:

Comparator<Duck> cmp = Comparator
    .comparing((Duck duck)-> duck.weight)
    .thenComparing((Duck duck)-> duck.age)
    .thenComparing(duck-> duck.name);

类型推断不适用于隐式 lambda,因此您必须指定前两个 lambda 的参数类型。Brian Goetz 在这个答案中的更多细节。

于 2014-07-16T20:49:15.097 回答
14

首先,您的解决方案并没有那么慢。

如果你真的想要另一种方法,那么给每只鸭子一个“分数”,它本质上是一个单一的数字,是它们三个特征的总和,但重量权重很大(请原谅几乎不可避免的双关语),年龄较小; 和一个非常小的名字。

您可以为每个特征分配约 10 位,因此对于每个特征,您必须在范围内0..1023

score = ( (weight << 10) + age) << 10 + name;

这可能是完全不需要的,但无论如何:)

于 2011-11-07T12:30:34.817 回答
6

您可以使用Apache Commons Lang的 CompareToBuilder 。(它解释了可比性,但也适用于 Comparator)。

于 2011-11-07T12:26:24.857 回答
4

您可以使用BeanComparators来自 Commons BeanUtils 的链接:

Comparator comparator = new BeanComparator("weight", new BeanComparator("age"));

http://commons.apache.org/beanutils/v1.8.3/apidocs/org/apache/commons/beanutils/BeanComparator.html

于 2011-11-07T12:28:10.623 回答
4

我刚刚重写了您的代码,没有嵌套 else 语句。你现在喜欢吗?

@Override
public int compare(Duck d1, Duck d2){
    int weightCmp = d1.weight.compareTo(d2.weight);
    if (weightCmp != 0) {
        return weightCmp;
    }
    int ageCmp = d1.age.compareTo(d2.age);
    if (ageCmp != 0) {
        return ageCmp;
    } 

    return d1.name.compareTo(d2.age);
}
于 2011-11-07T12:29:14.343 回答