2011-11-07 69 views
47

我有一个Duck对象的集合,我想使用多个键对它们进行排序。使用多个键排序Java对象

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); 
    } 
}); 

嗯,我这样做的频率很高,但这种解决方案并没有正确的气味。它不能很好地扩展,很容易搞砸。当然,必须有更好的方法来使用多个键对鸭子进行分类!有人知道更好的解决方案吗?

编辑去除不必要的else分支

+6

它看起来不错,你可以删除一个级别的缩进,通过删除'else',因为在你返回的'if'中,所以这是不需要的。 – stivlo

+14

+1让你的鸭子顺序 – Rich

+0

核心Java本身没有优雅的解决方案吗? – Sid

回答

9

的Java 8解决方案:

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

万岁lambda表达式,方法引用和默认方法:)!太糟糕了,我们要定义getter方法,或者使用明确lambda表达式,就像这样:

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

类型推断不会隐lambda表达式,所以你必须指定前两个lambda表达式的参数类型。更多详细信息请参见this answer by Brian Goetz

48

Guava更优雅:

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

阿帕奇commons-lang也有类似的结构,CompareToBuilder

+1

谢谢,这正是我想要的! [Guava订购](http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/Ordering.html)类看起来不错,如果您已经有一些比较器并希望将它们结合起来并用它们进行排序。 – andras

+3

Apache的CompareToBuilder稍微优雅一点,因为它默认处理空值,使用空值第一次比较。除非您为每个.compare()调用添加第三个参数(Ordering.natural()。nullsFirst()),否则Guava的ComparisonChain将抛出NullPointerException。 –

+2

如果你知道,你喜欢空值。 –

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); 
} 
+0

是的,谢谢,它看起来更好,但主要问题是我手动链接比较。 Guava ComparisonChain和Apache CompareToBuilder看起来好多了。 – andras

14

首先,你的解决方案是不是缓慢。

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

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

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

这可能是完全不必要的,但无论:)

+0

好的小动作,谢谢。我是为了美观,而不是表现,但我会牢记这一点:) – andras

20
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(); 
    } 
}); 
+0

我如何使用它来分类Arraylists?(使用多个键)? –

+0

你是在谈论数组(例如String [])还是'java.util.ArrayList's?它取决于存储的元素。数组可以使用方法'Arrays.sort(T [],比较器)' –

+0

'我的意思是,在这个例如,你刚刚排序的鸭类型的2个对象....“公共诠释比较(鸭o1,Duck o2)“如果我必须对Duck类型的整数列表进行排序,例如ArrayList dd = new ArrayList(); ????? –