2

I'm interested in sorting an list of object based on date attribute in that object. I can either use list sort method.

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

Or I can use stream sorted method

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

Out of both above option which should we use and why?

I know the former one will update my original list and later one will not update the original but instead give me a fresh new list object.

So, I don't care my original list is getting updated or not. So which one is good option and why?

Pshemo
  • 122,468
  • 25
  • 185
  • 269
Pankaj
  • 61
  • 1
  • 1
  • 3

5 Answers5

2

If you only need to sort your List, and don't need any other stream operations (such as filtering, mapping, etc...), there's no point in adding the overhead of creating a Stream and then creating a new List. It would be more efficient to just sort the original List.

Eran
  • 387,369
  • 54
  • 702
  • 768
1

Streams have some overheads because it creates many new objects like a concrete Stream, a Collector, and a new List. So if you just want to sort a list and doesn't care about whether the original gets changed or not, use List.sort.

There is also Collections.sort, which is an older API. The difference between it and List.sort can be found here.

Stream.sorted is useful when you are doing other stream operations alongside sorting.

Your code can also be rewritten with Comparator:

list.sort(Comparator.comparing(YourClass::getDate)));
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • 3
    while this is somehow true - that "overhead" is usually minor; also a `Stream::sorted` can potentially tell that the initial List is _already_ sorted and do nothing, while a `List::sort` will still try to sort – Eugene May 05 '19 at 11:04
  • 2
    *There is also Collections.sort, which just calls List.sort* - also this is a bit misleading. It does that, _but_ `List::sort` in the default implementation will 1) copy the array 2) sort it 3) copy back; while a concrete implementation (like `ArrayList`) will sort in place the inner array; so that part in your answer *which just calls* is a bitt off – Eugene May 05 '19 at 11:21
  • @Eugene a Stream can only know that a source List is already sorted, if the list itself is already aware of it, which allows making `List::sort` a no-op as well. – Holger May 06 '19 at 09:31
1

If you wish to known which is best, your best option is to benchmark it: you may reuse my answer JMH test.

It should be noted that:

  • List::sort use Arrays::sort. It create an array before sorting. It does not exists for other Collection.
  • Stream::sorted is done as state full intermediate operation. This means the Stream need to remember its state.

Without benchmarking, I'd say that:

  • You should use collection.sort(). It is easier to read: collection.stream().sorted().collect(toList()) is way to long to read and unless you format your code well, you might have an headache (I exaggerate) before understanding that this line is simply sorting.
  • sort() on a Stream should be called:
    • if you filter many elements making the Stream effectively smaller in size than the collection (sorting N items then filtering N items is not the same than filtering N items then sorting K items with K <= N).
    • if you have a map transformation after the sort and you loose a way to sort using the original key.

If you use your stream with other intermediate operation, then sort might be required / useful:

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

In that example, the filter apply before the sort: the stream #1 is smaller than #0, so the cost of sorting with stream might be less than Collections.sort().

If all that you do is simply filtering, you may also use a TreeSet or a collectingAndThen operation:

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

Or:

collection.stream() // Stream<U>
          .filter(...) // Stream<U>
          .collect(collectingAndThen(toList(), list -> {
            list.sort(); 
            return list;
          })); // List<V>
NoDataFound
  • 11,381
  • 33
  • 59
  • 1
    Might be worth noting that for all builtin implementations, all sort operation will end up at the same algorithm. So the only performance differences arise from copying operations. In case of the commonly used `ArrayList`, it will override `sort` to pass its internal array to `Arrays.sort`, so that’s the most efficient solution imaginable, as long as the source *is* already an `ArrayList`. – Holger May 06 '19 at 09:28
0

First one would be better in term of performance. In the first one, the sort method just compares the elements of the list and orders them. The second one will create a stream from your list, sort it and create a new list from that stream.

In your case, since you can update the first list, the first approach is the better, both in term of performance and memory consumption. The second one is convenient if you need to and with a stream, or if you have a stream and want to end up with a sorted list.

Paragoumba
  • 88
  • 1
  • 10
0

You use the first method

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

it's much faster than the second one and it didn't create a new intermediate object. You could use the second method when you want to do some additional stream operations (e.g. filtering, map).

Lungu Daniel
  • 816
  • 2
  • 8
  • 15