In this tutorial, I will demonstrate how to sort a list of integers using the Java Stream API. Sorting is a common operation when working with collections, and it is frequently discussed in Java coding interviews.
Java provides several ways to sort a list, including Collections.sort(), List.sort(), and the Stream API. The Stream API offers a concise way to process a collection and produce a sorted result without modifying the original list.
The sorted() method sorts stream elements in their natural order. For integers, natural order means ascending numerical order. You can also provide a Comparator to sort numbers in descending order or apply a custom sorting rule.
In this tutorial, you will learn how to sort integers in ascending and descending order, how to use custom comparators, and how Stream sorting differs from sorting a list directly.
1. Problem Statement
Write a Java program to sort a list of integers using the Stream API.
For example, consider the following list:
Input: [50, 10, 40, 20, 30] Ascending Order: [10, 20, 30, 40, 50] Descending Order: [50, 40, 30, 20, 10]
The program should demonstrate both ascending and descending sorting using Java Streams.
2. Method 1: Sort Integers in Ascending Order Using Stream.sorted()
The simplest approach is to use the sorted() method.
For a stream of integers, this method sorts the elements in ascending order according to their natural ordering.
Java Program
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class SortIntegersAscending {
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(50, 10, 40, 20, 30);
System.out.println("Original List: " + numbers);
// Sort integers in ascending order
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Ascending Order: "
+ sortedNumbers);
}
}
Output
Original List: [50, 10, 40, 20, 30] Ascending Order: [10, 20, 30, 40, 50]
Step-by-Step Explanation
Step 1: Create a List of Integers
List<Integer> numbers =
Arrays.asList(50, 10, 40, 20, 30);
This statement creates a list containing five integers in an unsorted order.
Step 2: Convert the List into a Stream
numbers.stream()
The stream() method creates a sequential stream from the list.
Step 3: Sort the Elements
.sorted()
The sorted() method arranges the integers in ascending numerical order.
Because Integer implements Comparable, Java can determine the natural ordering of its values.
Step 4: Collect the Sorted Elements
.collect(Collectors.toList());
This terminal operation collects the sorted stream elements into a List.
The original list remains unchanged because sorting the stream does not modify the source collection.
3. Method 2: Sort Integers in Descending Order Using Stream.sorted()
To sort integers from largest to smallest, provide a Comparator to the sorted() method.
Java provides Comparator.reverseOrder(), which reverses the natural ordering of elements.
Java Program
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class SortIntegersDescending {
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(50, 10, 40, 20, 30);
System.out.println("Original List: " + numbers);
// Sort integers in descending order
List<Integer> sortedNumbers = numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println("Descending Order: "
+ sortedNumbers);
}
}
Output
Original List: [50, 10, 40, 20, 30] Descending Order: [50, 40, 30, 20, 10]
How Comparator.reverseOrder() Works
The Comparator.reverseOrder() method returns a Comparator that applies the reverse of the elements' natural ordering.
For integer values:
- Natural order arranges numbers from smallest to largest.
- Reverse order arranges numbers from largest to smallest.
This approach is simple, readable, and commonly used in modern Java applications.
4. Method 3: Sort Integers Using Stream.toList() in Java 16+
Starting with Java 16, the Stream API provides the toList() method, which makes collecting stream results shorter.
You can use it instead of collect(Collectors.toList()) when an unmodifiable result list is acceptable.
Java Program Using Stream.toList()
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class SortIntegersUsingToList {
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(50, 10, 40, 20, 30);
// Ascending order
List<Integer> ascending = numbers.stream()
.sorted()
.toList();
// Descending order
List<Integer> descending = numbers.stream()
.sorted(Comparator.reverseOrder())
.toList();
System.out.println("Original List: " + numbers);
System.out.println("Ascending Order: " + ascending);
System.out.println("Descending Order: " + descending);
}
}
Output
Original List: [50, 10, 40, 20, 30] Ascending Order: [10, 20, 30, 40, 50] Descending Order: [50, 40, 30, 20, 10]
Important: Stream.toList() Returns an Unmodifiable List
The List returned by Stream.toList() is unmodifiable.
For example, the following code throws an UnsupportedOperationException:
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.toList();
// Throws UnsupportedOperationException
sortedNumbers.add(60);
If you need a modifiable ArrayList, create one explicitly:
import java.util.ArrayList;
ArrayList<Integer> sortedNumbers = new ArrayList<>(
numbers.stream().sorted().toList()
);
sortedNumbers.add(60);
This creates a mutable ArrayList containing the sorted elements.
5. Method 4: Sort Integers Using a Custom Comparator
You can also provide a custom Comparator using a lambda expression.
For example, the following program sorts integers in descending order using Integer.compare().
Java Program
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class SortUsingCustomComparator {
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(50, 10, 40, 20, 30);
List<Integer> descending = numbers.stream()
.sorted((a, b) -> Integer.compare(b, a))
.collect(Collectors.toList());
System.out.println("Descending Order: "
+ descending);
}
}
Output
Descending Order: [50, 40, 30, 20, 10]
The expression Integer.compare(b, a) compares the second value with the first, producing descending order.
Important: Prefer Integer.compare(b, a) over b - a. Subtracting integers in a Comparator can overflow for extreme values and produce an incorrect ordering.
6. Dry Run: Sorting Integers in Ascending Order
Consider the following list:
Original List: [50, 10, 40, 20, 30]
When the program executes the stream pipeline, the sorted() operation arranges the elements according to their natural order.
| Operation | Purpose | Result |
|---|---|---|
| Create List | Store integer elements | [50, 10, 40, 20, 30] |
| stream() | Create a sequential stream | Elements: 50, 10, 40, 20, 30 |
| sorted() | Apply natural ordering | 10, 20, 30, 40, 50 |
| collect() | Create a result List | [10, 20, 30, 40, 50] |
The original list remains unchanged throughout the operation.
7. Sort a List Containing Duplicate and Negative Integers
The Stream API also handles duplicate values, negative numbers, and zero correctly.
Java Program
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class SortNegativeAndDuplicateNumbers {
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(10, -5, 30, 10, 0, -20, 30);
List<Integer> ascending = numbers.stream()
.sorted()
.toList();
List<Integer> descending = numbers.stream()
.sorted(Comparator.reverseOrder())
.toList();
System.out.println("Original List: " + numbers);
System.out.println("Ascending Order: " + ascending);
System.out.println("Descending Order: " + descending);
}
}
Output
Original List: [10, -5, 30, 10, 0, -20, 30] Ascending Order: [-20, -5, 0, 10, 10, 30, 30] Descending Order: [30, 30, 10, 10, 0, -5, -20]
Notice that sorting does not remove duplicate elements. The repeated values remain in the sorted result.
8. Time and Space Complexity
The sorted() operation sorts the stream elements using their natural ordering or the supplied Comparator.
| Operation | Time Complexity | Additional Space |
|---|---|---|
| Ascending Stream sorting | O(n log n) | O(n) |
| Descending Stream sorting | O(n log n) | O(n) |
| Custom Comparator sorting | O(n log n) | O(n) |
Here, n represents the number of elements in the list.
Sorting generally requires O(n log n) time for arbitrary input. The Stream API also uses additional memory to process sorted elements and create the resulting list.
The sorted() method is a stateful intermediate operation. It must consider the elements of the stream before producing them in sorted order.
9. Stream.sorted() vs. Collections.sort() vs. List.sort()
Java provides multiple sorting methods. The best choice depends on whether you want to modify the original list or create a sorted result.
| Feature | Stream.sorted() | Collections.sort() | List.sort() |
|---|---|---|---|
| Modifies original list | No | Yes | Yes |
| Returns sorted elements | Yes, through a stream | No, sorts in place | No, sorts in place |
| Supports Comparator | Yes | Yes | Yes |
| Best use case | Stream processing pipelines | Traditional mutable list sorting | Direct mutable list sorting |
Use Stream.sorted() when you want to keep the original list unchanged or combine sorting with other stream operations.
If you simply need to sort a mutable list directly, List.sort() is often a more straightforward choice.
10. Important Edge Cases
Case 1: Empty List
Input: [] Output: []
The Stream API handles an empty list without errors.
Case 2: Single Element
Input: [42] Output: [42]
A list containing one element is already sorted.
Case 3: Already Sorted List
Input: [10, 20, 30, 40] Ascending Output: [10, 20, 30, 40]
The values remain in ascending order.
Case 4: Duplicate Values
Input: [30, 10, 20, 10, 30] Ascending Output: [10, 10, 20, 30, 30]
Duplicate elements are preserved.
Case 5: List Contains Null Values
Natural ordering does not support comparing null Integer values. Calling sorted() on a list containing null elements can throw a NullPointerException.
If null values are expected, use a null-aware Comparator:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class SortIntegersWithNulls {
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(30, null, 10, 20, null);
List<Integer> sorted = numbers.stream()
.sorted(Comparator.nullsLast(
Comparator.naturalOrder()))
.toList();
System.out.println(sorted);
}
}
Output
[10, 20, 30, null, null]
The Comparator.nullsLast() method places null values after all non-null integers.
11. Common Mistakes to Avoid
Mistake 1: Forgetting the Terminal Operation
The sorted() method is an intermediate operation. Stream pipelines are evaluated when a terminal operation runs.
To store the sorted values, collect the stream into a list:
List<Integer> sorted = numbers.stream()
.sorted()
.toList();
Mistake 2: Assuming the Original List Is Modified
The Stream API does not sort the original list in place.
If you want to replace the original list variable with the sorted result, you must assign the result explicitly. If you need to modify the existing list object, use List.sort().
Mistake 3: Using Subtraction in a Comparator
Avoid writing:
.sorted((a, b) -> b - a)
Integer subtraction may overflow for extreme values.
Instead, use:
.sorted((a, b) -> Integer.compare(b, a))
Or simply:
.sorted(Comparator.reverseOrder())
Mistake 4: Trying to Modify a List Returned by Stream.toList()
Remember that Stream.toList() returns an unmodifiable list.
Create a new ArrayList if you need to add, remove, or replace elements later.
12. Frequently Asked Questions
Q1. How do I sort a list of integers using Java Streams?
Use numbers.stream().sorted().toList() to sort integers in ascending order. This form requires Java 16 or later.
Q2. How do I sort integers in descending order using Java Streams?
Use numbers.stream().sorted(Comparator.reverseOrder()).toList().
Q3. Does Stream.sorted() modify the original list?
No. It produces a sorted stream of elements without changing the original list.
Q4. What is the difference between sorted() and sort() in Java?
The sorted() method operates on streams, while List.sort() sorts a mutable list directly.
Q5. What is the time complexity of Stream.sorted()?
The typical time complexity for sorting an arbitrary list of integers is O(n log n).
Q6. Can Stream.sorted() handle duplicate elements?
Yes. Duplicate values are preserved. Sorting does not remove duplicates.
Q7. Can I remove duplicates and sort integers in one stream pipeline?
Yes. Combine distinct() and sorted():
List<Integer> result = numbers.stream()
.distinct()
.sorted()
.toList();
This removes duplicate integers and returns the remaining values in ascending order.
13. Conclusion
In this tutorial, you learned how to sort a list of integers using the Java Stream API.
- Ascending order: Use
stream().sorted(). - Descending order: Use
stream().sorted(Comparator.reverseOrder()). - Custom sorting: Pass a Comparator to the
sorted()method. - Java 16 and later: Use
toList()to collect an unmodifiable result. - Java 8 compatibility: Use
collect(Collectors.toList()).
The Stream API is particularly useful when sorting is part of a larger data-processing operation. You can combine sorting with filtering, mapping, and removing duplicates to create readable collection-processing pipelines.
For Java coding interviews, I recommend understanding both Stream.sorted() and List.sort(), including the difference between creating a sorted result and modifying an existing list.
14. Official Java Documentation
For more information about the Java APIs used in this tutorial, refer to the official Oracle documentation:
- Java Stream Interface — Documentation for
sorted(),distinct(), andtoList(). - Java Comparator Interface — Documentation for custom ordering,
reverseOrder(), and null-aware comparators. - Java Collectors Class — Documentation for collecting stream results.
- Java List Interface — Documentation for list operations, including
sort(). - Java Collections Class — Documentation for traditional collection sorting methods.
Continue Learning with Java Guides
Want to take your programming skills to the next level? Explore my practical Udemy courses on Java, Spring Boot, Microservices, Full-Stack Development, Docker, and Generative AI. You can also follow my structured learning roadmaps to become a professional developer.
By Ramesh Fadatare - Udemy Instructor Partner
Comments
Post a Comment
Leave Comment