【问题标题】:What's the difference between list interface sort method and stream interface sorted method?列表接口排序方法和流接口排序方法有什么区别?
【发布时间】:2019-05-05 10:43:51
【问题描述】:

我有兴趣根据对象中的日期属性对对象列表进行排序。我可以使用列表排序方法。

list.sort( (a, b) -> a.getDate().compareTo(b.getDate()) );

或者我可以使用流排序方法

List<E> l = list.stream()
                .sorted( (a, b) -> a.getDate().compareTo(b.getDate()))
                .collect(Collectors.toList());

我们应该使用以上两个选项,为什么?

我知道前一个会更新我的原始列表,而后一个不会更新原始列表,而是给我一个全新的列表对象。

所以,我不在乎我的原始列表是否更新。那么哪一个是好的选择,为什么?

【问题讨论】:

    标签: java list sorting java-8 java-stream


    【解决方案1】:

    如果您只需要对您的List 进行排序,并且不需要任何其他流操作(例如过滤、映射等),那么添加创建Stream 和然后创建一个新的List。只对原始的List 进行排序会更有效。

    【讨论】:

      【解决方案2】:

      如果您想知道哪个最好,最好的选择是对其进行基准测试:您可以重复使用我的answer JMH test

      需要注意的是:

      • List::sort 使用 Arrays::sort。它在排序之前创建一个数组。其他Collection 不存在。
      • Stream::sorted 是作为状态完全中间操作完成的。这意味着Stream 需要记住它的状态。

      如果没有基准测试,我会说:

      • 您应该使用collection.sort()。更容易阅读:collection.stream().sorted().collect(toList()) 读起来太长了,除非你很好地格式化你的代码,否则在理解这一行只是排序之前,你可能会感到头疼(我夸大了)。
      • Stream 上的 sort() 应该被称为:
        • 如果您过滤了许多元素,使 Stream 的大小实际上小于集合(排序 N 项然后过滤 N 项过滤 N 项然后排序 K 项不同K )。
        • 如果您在排序后进行了地图转换,并且您失去了使用原始键进行排序的方法。

      如果您将流与其他中间操作一起使用,那么sort 可能是必需的/有用的:

      collection.stream()    // Stream<U> #0
                .filter(...) // Stream<U> #1
                .sorted()      // Stream<U> #2
                .map(...)    // Stream<V> #3
                .collect(toList()) // List<V> sorted by U.
                ;
      

      在该示例中,过滤器在排序之前应用:流 #1 小于 #0,因此使用流进行排序的成本可能低于 Collections.sort()。

      如果您所做的只是过滤,您还可以使用TreeSetcollectingAndThen 操作:

      collection.stream()    // Stream<U> #0
                .filter(...) // Stream<U> #1
                .collect(toCollection(TreeSet::new))
                ;
      

      或者:

      collection.stream() // Stream<U>
                .filter(...) // Stream<U>
                .collect(collectingAndThen(toList(), list -> {
                  list.sort(); 
                  return list;
                })); // List<V>
      

      【讨论】:

      • 可能值得注意的是,对于所有内置实现,所有排序操作都将以相同的算法结束。因此,唯一的性能差异来自复制操作。对于常用的ArrayList,它将覆盖sort,将其内部数组传递给Arrays.sort,因此这是可以想象的最有效的解决方案,只要源已经是@ 987654341@.
      【解决方案3】:

      流有一些开销,因为它会创建许多新对象,例如具体的StreamCollector 和新的List。因此,如果您只想对列表进行排序而不关心原始列表是否被更改,请使用List.sort

      还有Collections.sort,这是一个较旧的 API。它和List.sort的区别可以看here

      Stream.sorted 在排序的同时进行其他流操作时很有用。

      你的代码也可以用Comparator重写:

      list.sort(Comparator.comparing(YourClass::getDate)));
      

      【讨论】:

      • 虽然这在某种程度上是正确的——“开销”通常是次要的; Stream::sorted 也可以潜在地告诉初始列表 已经 排序并且什么都不做,而 List::sort 仍然会尝试排序
      • 还有 Collections.sort,它只调用 List.sort - 这也有点误导。它会这样做,但是 List::sort 在默认实现中将 1) 复制数组 2) 对其进行排序 3) 复制回来;而具体的实现(如ArrayList)将对内部数组进行排序;所以你的答案只是调用的那部分有点过分了
      • @Eugene a Stream 只能知道源列表已经排序,如果列表本身已经知道它,这也允许使 List::sort 成为空操作。
      【解决方案4】:

      第一个在性能方面会更好。在第一个中,sort 方法只是比较列表的元素并对它们进行排序。第二个将从您的列表中创建一个流,对其进行排序并从该流中创建一个新列表。

      在您的情况下,由于您可以更新第一个列表,因此第一种方法在性能和内存消耗方面都更好。如果您需要和使用流,或者如果您有流并希望以排序列表结束,则第二个很方便。

      【讨论】:

        【解决方案5】:

        你用第一种方法

        list.sort((a, b) -> a.getDate().compareTo(b.getDate()));
        

        它比第二个快得多,并且没有创建新的中间对象。当您想做一些额外的流操作(例如过滤、映射)时,您可以使用第二种方法。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-11
          • 2015-03-06
          • 1970-01-01
          • 2013-06-18
          • 2015-10-25
          • 1970-01-01
          相关资源
          最近更新 更多