Sorting lists is a fundamental operation in programming, and Java provides powerful tools to accomplish this efficiently. With the introduction of Java 8, sorting lists became even more streamlined and expressive, thanks to features like lambda expressions and the Comparator
interface enhancements. This article delves into the intricacies of using sort
with Comparator
in Java, providing practical examples and insights to elevate your Java programming skills.
Sorting Lists of Strings Alphabetically in Java
Let’s begin with a common scenario: sorting a list of strings alphabetically. Java offers several ways to achieve this, catering to different needs like case sensitivity.
Consider the following list of cities:
List<String> cities = Arrays.asList( "Milan", "london", "San Francisco", "Tokyo", "New Delhi" );
System.out.println(cities);
// Output: [Milan, london, San Francisco, Tokyo, New Delhi]
To sort this list alphabetically, ignoring case, we can utilize String.CASE_INSENSITIVE_ORDER
. This Comparator
treats uppercase and lowercase letters as the same for sorting purposes.
cities.sort(String.CASE_INSENSITIVE_ORDER);
System.out.println(cities);
// Output: [london, Milan, New Delhi, San Francisco, Tokyo]
Alternatively, if case sensitivity is important, Comparator.naturalOrder()
comes into play. This comparator sorts strings in their natural order, where uppercase letters come before lowercase letters.
cities.sort(Comparator.naturalOrder());
System.out.println(cities);
// Output: [Milan, New Delhi, San Francisco, Tokyo, london]
In essence, Java 8 introduced the List.sort()
method, a significant improvement over the older Collections.sort()
. List.sort()
directly operates on the list itself and accepts a Comparator
to define the sorting logic. This change makes the code cleaner and more object-oriented.
Sorting Lists of Integers in Java
Sorting lists of integers is equally straightforward. Comparator.naturalOrder()
works seamlessly for numeric types as well, arranging numbers in ascending order.
Let’s take a list of numbers:
List<Integer> numbers = Arrays.asList(6, 2, 1, 4, 9);
System.out.println(numbers);
// Output: [6, 2, 1, 4, 9]
Applying Comparator.naturalOrder()
sorts them in ascending order:
numbers.sort(Comparator.naturalOrder());
System.out.println(numbers);
// Output: [1, 2, 4, 6, 9]
Sorting Lists of Objects by String Fields in Java
Often, you’ll need to sort lists of objects based on one of their string fields. Consider a Movie
class with a title
field:
class Movie {
private String title;
public Movie(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
@Override
public String toString() {
return "Movie{title='" + title + "'}";
}
}
To sort a list of Movie
objects by title, we can use Comparator.comparing()
. This powerful method takes a function that extracts the sorting key – in this case, the getTitle
method of the Movie
class.
List<Movie> movies = Arrays.asList(
new Movie("Lord of the rings"),
new Movie("Back to the future"),
new Movie("Carlito's way"),
new Movie("Pulp fiction")
);
movies.sort(Comparator.comparing(Movie::getTitle));
movies.forEach(System.out::println);
This code snippet will produce the following sorted output:
Movie{title='Back to the future'}
Movie{title='Carlito's way'}
Movie{title='Lord of the rings'}
Movie{title='Pulp fiction'}
The magic behind Comparator.comparing()
lies in its ability to leverage the natural ordering of the extracted key. Since title
is a String
, which implements the Comparable
interface, Comparator.comparing()
implicitly uses the compareTo
method of the String
class to sort the Movie
objects.
Sorting Lists of Objects by Double Fields in Java
Similarly, you might need to sort objects by double fields. Let’s add a rating
field of type double
to our Movie
class:
class Movie {
// ... previous code ...
private double rating;
public Movie(String title, double rating) {
this.title = title;
this.rating = rating;
}
public double getRating() {
return rating;
}
@Override
public String toString() {
return "Movie{title='" + title + "', rating=" + rating + "}";
}
}
To sort movies by rating in descending order (highest-rated movies first), we can employ Comparator.comparingDouble()
in conjunction with reversed()
. Comparator.comparingDouble()
is specifically designed for double fields, and reversed()
inverts the natural sort order (ascending to descending).
List<Movie> movies = Arrays.asList(
new Movie("Lord of the rings", 8.8),
new Movie("Back to the future", 8.5),
new Movie("Carlito's way", 7.9),
new Movie("Pulp fiction", 8.9)
);
movies.sort(Comparator.comparingDouble(Movie::getRating)
.reversed());
movies.forEach(System.out::println);
The output will be:
Movie{title='Pulp fiction', rating=8.9}
Movie{title='Lord of the rings', rating=8.8}
Movie{title='Back to the future', rating=8.5}
Movie{title='Carlito's way', rating=7.9}
Comparator.comparingDouble()
utilizes Double.compare()
under the hood for efficient double comparison. For int
and long
fields, you can use comparingInt()
and comparingLong()
respectively.
Sorting Lists with Custom Comparators in Java
For more complex sorting scenarios, you might need to define your own custom Comparator
. Let’s add a boolean field starred
to our Movie
class:
class Movie {
// ... previous code ...
private boolean starred;
public Movie(String title, double rating, boolean starred) {
this.title = title;
this.rating = rating;
this.starred = starred;
}
public boolean getStarred() {
return starred;
}
@Override
public String toString() {
return "Movie{starred=" + starred + ", title='" + title + "', rating=" + rating + "}";
}
}
Suppose we want to sort movies such that starred movies appear at the top of the list. We can create a custom Comparator
to achieve this.
List<Movie> movies = Arrays.asList(
new Movie("Lord of the rings", 8.8, true),
new Movie("Back to the future", 8.5, false),
new Movie("Carlito's way", 7.9, true),
new Movie("Pulp fiction", 8.9, false)
);
movies.sort(new Comparator<Movie>() {
@Override
public int compare(Movie m1, Movie m2) {
if (m1.getStarred() == m2.getStarred()) {
return 0; // If starred status is the same, order doesn't matter
}
return m1.getStarred() ? -1 : 1; // Starred movies come first
}
});
movies.forEach(System.out::println);
This anonymous class implementation of Comparator
prioritizes starred movies. The output reflects this custom sorting logic:
Movie{starred=true, title='Lord of the rings', rating=8.8}
Movie{starred=true, title='Carlito's way', rating=7.9}
Movie{starred=false, title='Back to the future', rating=8.5}
Movie{starred=false, title='Pulp fiction', rating=8.9}
Java 8’s lambda expressions offer a more concise way to express custom comparators:
movies.sort((m1, m2) -> {
if (m1.getStarred() == m2.getStarred()) {
return 0;
}
return m1.getStarred() ? -1 : 1;
});
Furthermore, we can even use Comparator.comparing()
with a lambda for the custom comparison logic:
movies.sort(Comparator.comparing(Movie::getStarred, (star1, star2) -> {
if (star1 == star2) {
return 0;
}
return star1 ? -1 : 1;
}));
This approach separates the key extraction (getting the starred
status) from the comparison logic, making the code potentially more readable in complex scenarios.
Chaining Comparators for Multi-Level Sorting in Java
For sophisticated sorting requirements, you can chain comparators. Let’s say we want to sort movies first by starred
status (starred movies first) and then by rating
in descending order within each starred group. thenComparing()
allows us to chain comparators.
List<Movie> movies = Arrays.asList(
new Movie("Lord of the rings", 8.8, true),
new Movie("Back to the future", 8.5, false),
new Movie("Carlito's way", 7.9, true),
new Movie("Pulp fiction", 8.9, false)
);
movies.sort(Comparator.comparing(Movie::getStarred)
.reversed() // Reverse starred order (true first is default)
.thenComparing(Comparator.comparing(Movie::getRating)
.reversed())); // Then sort by rating descending
movies.forEach(System.out::println);
The output demonstrates the chained sorting:
Movie{starred=true, title='Lord of the rings', rating=8.8}
Movie{starred=true, title='Carlito's way', rating=7.9}
Movie{starred=false, title='Pulp fiction', rating=8.9}
Movie{starred=false, title='Back to the future', rating=8.5}
Here, we first sort by starred
status (using reversed()
because comparing(Movie::getStarred)
would put false
first). Then, within each group of starred/unstarred movies, we sort by rating
in descending order using thenComparing(Comparator.comparing(Movie::getRating).reversed())
.
Conclusion
Java 8’s List.sort()
method combined with the Comparator
interface offers a flexible and powerful approach to sorting lists. From simple alphabetical and numerical sorting to complex multi-level sorting with custom logic, Java provides the tools to handle diverse sorting needs efficiently. Understanding and utilizing Comparator.naturalOrder()
, String.CASE_INSENSITIVE_ORDER
, comparing()
, comparingDouble()
, custom comparators, reversed()
, and thenComparing()
will significantly enhance your ability to manipulate and organize data in Java applications. By mastering these techniques, you can write cleaner, more readable, and more efficient sorting code.