Mastering Sort List Comparator in Java: A Comprehensive Guide

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.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *