【问题标题】:Sorting Java objects using multiple keys使用多个键对 Java 对象进行排序
【发布时间】:2011-12-23 14:23:16
【问题描述】:

我有一组 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 进行排序!有人知道更好的解决方案吗?

EDIT 删除了不必要的else 分支

【问题讨论】:

  • 看起来还不错,你可以去掉一层缩进,去掉else,因为在if你返回,所以不需要。
  • +1 让你的鸭子井然有序
  • 核心Java本身就没有优雅的解决方案吗?
  • 嘿,我有一个问题要问你......我完全不知道这个比较的东西是如何工作的......你的对象“鸭子”在排序后会被覆盖吗??
  • Collections.sort() 修改给定集合本身(而不是返回集合的排序副本)。因此,ducks 对象“被覆盖”,因为它是就地排序的。 (实际上不是就地:ducks 的内容被转储到一个数组中,在那里它被排序,结果被复制回原始集合。)

标签: java sorting comparator comparable


【解决方案1】:

Guava 更优雅:

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

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

【讨论】:

  • 谢谢,这正是我想要的! Guava Ordering 类看起来也不错,如果您已经有一些比较器并且想要组合它们并使用它们进行排序。
  • Apache 的 CompareToBuilder 稍微优雅一点,因为它默认也处理空值,使用空值优先比较。 Guava 的 ComparisonChain 将抛出 NullPointerException,除非您在每个 .compare() 调用中添加第三个参数 (Ordering.natural().nullsFirst())。
  • 如何使用它对 Arraylists 进行排序?(使用多个键)?
  • @JB Nizet 有什么办法可以将它用作整个数组???这里只有 2 个对象正在排序
  • 您创建一个比较器,并在比较器的 compare() 方法中使用上面的代码。然后使用Collections.sort(comparator) 对列表进行排序,如问题所示。
【解决方案2】:
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();
  }
});

【讨论】:

  • 如何使用它对 Arraylists 进行排序?(使用多个键)?
  • 您说的是数组(例如 String[])还是java.util.ArrayLists?这取决于存储的元素。数组可以使用Arrays.sort(T[], Comparator&lt;T&gt;)方法进行排序
  • 我的意思是,在这个例子中,你刚刚对鸭子类型的 2 个对象进行了排序......“public int compare(Duck o1,​​ Duck o2)”如果我必须对一个Duck 类型的整个数组列表..例如 ArrayList dd =new ArrayList(); ?????
  • 这就是为什么将Comparator 实现传递给Collections.sort(List&lt;Duck&gt;, Comparator&lt;Duck&gt;) 方法,该方法负责对整个列表进行排序。比较器用于比较列表中的任意两个元素。
  • @IgorGanapolsky 将库添加到您的类路径 mvnrepository.com/artifact/org.apache.commons/commons-lang3
【解决方案3】:

Java 8 解决方案:

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

万岁 lambdas、方法引用和默认方法:)!太糟糕了,我们必须定义 getter,或者使用 explicit lambdas,像这样:

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

【讨论】:

    【解决方案4】:

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

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

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

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

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

    【讨论】:

    • 不错的小技巧,谢谢。我追求的是美丽,而不是性能,但我会记住这一点:)
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

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

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

      【讨论】:

        【解决方案7】:

        我刚刚重写了您的代码,没有嵌套 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);
        }
        

        【讨论】:

        • 是的,谢谢,它看起来确实更好,但主要问题是我手动链接比较。 Guava ComparisonChain 和 Apache CompareToBuilder 看起来好多了。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-12-13
        • 1970-01-01
        • 2010-11-11
        • 2010-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多