Blog

Sorting Java ArrayLists Like a Pro: Collections vs Streams Compared

Sorting collections in Java trips up developers at every level. The language gives you multiple tools, and choosing between Collections.sort() and Stream-based sorting isn't always obvious.

Both approaches work, but they behave differently and suit different scenarios. Getting this right matters more than you might think when working with real applications.

Collections.sort() Modifies the Original List

Collections.sort() reorders elements in place. The method takes your existing list and rearranges it directly. No new list gets created.

This matters when other parts of your code hold references to the same list object. Sorting in place affects all those references simultaneously. Sometimes that's what you want. Sometimes it isn't.

The syntax leans on Comparator objects to define sort order. Comparator.comparing() chains nicely, letting you sort by multiple fields with thenComparing().

Streams Create a New Sorted List

Stream.sorted() returns a new stream rather than modifying the source. Your original list stays untouched. This immutability-friendly approach prevents surprises when multiple parts of your code share the same collection.

The trade-off is memory. Streams process elements lazily and create intermediate objects. For small lists, the difference is negligible. For massive collections processing in tight loops, the overhead adds up.

Sorting by Single and Multiple Fields

Single-field sorting works identically for both approaches. Pass a Comparator specifying the field, and your collection orders correctly.

Multiple-field sorting unlocks more power. Chain thenComparing() calls to establish primary, secondary, and tertiary sort keys. A list of book objects might sort first by author, then by rating, then by name—all with one Comparator chain.

Combining ascending and descending order across fields requires slightly more logic. Reversing a Comparator handles direction for individual fields without rewriting your comparison logic.

Practical Example

You maintain a library application tracking book objects with name, author, and rating fields. Users need books sorted by author for browsing, but also want highest-rated books first when searching.

When a user requests author-sorted browsing, you call Collections.sort(library, Comparator.comparing(Book::getAuthor)). The original list updates in place, and the UI reflects the new order immediately.

Want the reverse direction instead? Chain reversed() onto your Comparator. For a top-rated view, you'd pipe the collection through a stream, apply comparingDouble with reversed order, and collect the results into a fresh list. This leaves your source data intact for other operations.

Why This Matters for Your Code

Choosing the right approach prevents subtle bugs. If a method modifies a list you pass in, callers might experience unexpected behavior. Streams sidestep this by honoring immutability. Collections.sort() works fine when modification is intentional and documented.

For Android development specifically, remember that Streams require API 24+. Most modern devices support this, but budget phones on older Android versions may not. Collections.sort() works everywhere.

Key takeaways

  • Collections.sort() modifies the original list; Stream.sorted() creates a new one
  • Use streams when you need immutability or chaining with other operations
  • Use Collections.sort() for simple in-place sorting on older Android targets
  • Multiple fields sort with chained thenComparing() calls

Frequently asked questions

Which sorting approach is faster in Java?

For small to medium lists, the difference is negligible. Collections.sort() uses TimSort and performs excellently. Streams add some overhead from creating intermediate objects, but the impact is minimal outside tight loops.

Can I sort in descending order using both approaches?

Yes. Append .reversed() to any Comparator to flip the order. With Collections.sort(), use Comparator.comparingDouble(Book::getRating).reversed(). With streams, the syntax is identical.

How do I sort by multiple fields with different directions?

Chain thenComparing() calls. For the first field, use Comparator.comparing(). For subsequent fields where you want descending order, use Comparator.comparingDouble().reversed() inside the thenComparing() call.

Does sorting affect null elements in my list?

By default, both approaches throw NullPointerException if the list contains null elements. Handle nulls explicitly using Comparator.nullsLast() or Comparator.nullsFirst().

What's the best approach for Android development?

Collections.sort() works universally and performs well. Streams require API 24+ but offer cleaner code for complex operations. Choose based on your minimum SDK target and whether you need immutability guarantees.

Related