Comparator interface in Java

Sorting is a common operation in programming, and Java provides several ways to sort objects. One such way is by using the Comparator interface, which is part of the Java Collections Framework. The Comparator interface allows for custom sorting of objects based on one or more fields of a custom object. In this tutorial, we will explore the Comparator interface in detail and how it can be used to sort custom objects.

What is Comparator interface in Java?

The Comparator interface in Java is part of the Collections Framework, and it allows us to sort objects. In the previous tutorial, we learned how to use the Comparable interface to sort custom objects based on a single field. However, by using the Comparator interface, we can have multiple sorting options and sort objects based on any criteria we choose.

The Comparator interface is located in the java.util package and consists of two methods:

  • public int compare(Object obj1, Object obj2) – Compares the first object with the second object.
  • public boolean equals(Object obj) – Compares the current object with the specified object.

It’s worth noting that the equals() method in the Comparator interface is not typically used in practice, as it is primarily designed to be used for unit testing and not as part of the actual comparison logic. In most cases, the compare() method is the only method that needs to be implemented when creating a Comparator.

Comparator vs Comparable

In Java, there are two interfaces that can be used for sorting objects: Comparator and Comparable. While both interfaces serve a similar purpose, there are some key differences between them that are important to understand.

Comparable

The Comparable interface is used to define the natural order of objects of a particular class. This is done by implementing the Comparable interface and overriding the compareTo() method. The compareTo() method should return a negative integer, zero, or a positive integer depending on whether the current object is less than, equal to, or greater than the specified object.

For example, let’s say we have a Person class that has a name field. We could implement the Comparable interface to define the natural order of Person objects based on their name as follows:

public class Person implements Comparable<Person> {
    private String name;
    // other fields and methods omitted for brevity

    @Override
    public int compareTo(Person other) {
        return this.name.compareTo(other.name);
    }
}

In this example, the compareTo() method compares the name field of the current Person object to the name field of the specified Person object.

Comparator

The Comparator interface, on the other hand, is used to define custom sorting logic for objects of a particular class that may not have a natural order. This is done by implementing the Comparator interface and overriding the compare() method. The compare() method should return a negative integer, zero, or a positive integer depending on whether the first object is less than, equal to, or greater than the second object.

For example, let’s say we have a Person class with name and age fields. We could implement a Comparator to sort Person objects by age as follows:

public class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        return Integer.compare(p1.getAge(), p2.getAge());
    }
}

In this example, the compare() method compares the age field of the two Person objects.

Differences

The main difference between the Comparable and Comparator interfaces is that Comparable is used to define the natural order of objects, whereas Comparator is used to define custom sorting logic that may not have a natural order.

Another difference is that the Comparable interface is implemented by the class of the objects being sorted, whereas the Comparator interface is implemented by a separate class that defines the sorting logic. This means that with Comparable, the sorting logic is tightly coupled to the class being sorted, whereas with Comparator, the sorting logic can be decoupled from the class being sorted and reused for other classes.

In general, it’s best to use Comparable when there is a natural order for the objects being sorted, and to use Comparator when there is no natural order or when custom sorting logic is required.

Sorting custom objects using the Comparator interface – Example

We can create a custom class for representing users by defining a new class named User:

class User {
    public int userId;
    public String username;
    public String address;

    public User(int userId, String username, String address) {
        this.userId = userId;
        this.username = username;
        this.address = address;
    }
}

To define the comparison logic based on the userId field, we create a class named UserIdComparator. This class implements the Comparator interface and defines the following comparison rules:

  • If the userId of the first object is greater than the second object, it returns 1.
  • If both userId fields are equal, it returns 0.
  • If the userId field of the first object is less than the second object, it returns -1.
class UserIdComparator implements Comparator<User> {
    public int compare(User user1, User user2) {
        if (user1.userId == user2.userId) {
            return 0;
        } else if (user1.userId > user2.userId) {
            return 1;
        } else {
            return -1;
        }
    }
}


We will now create a UsernameComparator class that establishes the comparison logic based on the username field. This class implements the Comparator interface, and since we are comparing Strings, we can utilize the compareTo() method of the String class instead of developing our own comparison logic.

class UsernameComparator implements Comparator<User> {
    public int compare(User user1, User user2) {
        return user1.username.compareTo(user2.username);
    }
}


We create a Test class where we implement the sorting logic by calling the Collections.sort() method and passing in the Comparator class as a parameter:

class Test {
    public static void main(String[] args) {
        List<User> users = new ArrayList<>();
        users.add(new User(5, "username4", "Address1"));
        users.add(new User(2, "username1", "Address2"));
        users.add(new User(7, "username2", "Address3"));
        users.add(new User(3, "username5", "Address4"));

        System.out.println("Before sorting:");
        for (User user : users) {
            System.out.println(user.userId + " " + user.username + " " + user.address);
        }

        // Sorting by userId field
        Collections.sort(users, new UserIdComparator());
        System.out.println("After sorting by userId field:");
        for (User user : users) {
            System.out.println(user.userId + " " + user.username + " " + user.address);
        }

        // Sorting by username field
        Collections.sort(users, new UsernameComparator());
        System.out.println("After sorting by username field:");
        for (User user : users) {
            System.out.println(user.userId + " " + user.username + " " + user.address);
        }
    }
}
Output:
Before sorting:
5 username4 Address1
2 username1 Address2
7 username2 Address3
3 username5 Address4

After sorting by userId field:
2 username1 Address2
3 username5 Address4
5 username4 Address1
7 username2 Address3

After sorting by username field:
2 username1 Address2
7 username2 Address3
5 username4 Address1
3 username5 Address4

Explore the use of lambda expressions in defining comparators

Java 8 introduced lambda expressions, which provide a concise way to define anonymous functions. This can be particularly useful when defining comparators, as it allows you to write the comparison logic inline without having to define a separate class.

Here’s an example of how you could define a Comparator for a custom object using a lambda expression:

List<User> users = new ArrayList<>();
// populate the list with User objects

Collections.sort(users, (u1, u2) -> u1.getUsername().compareTo(u2.getUsername()));

In this example, the lambda expression (u1, u2) -> u1.getUsername().compareTo(u2.getUsername()) defines a function that takes two User objects as input and returns an integer representing their order. Specifically, it compares the username field of each object using the compareTo() method of the String class.

By using a lambda expression, we avoid having to define a separate Comparator class and can write the comparison logic inline, making the code more concise and easier to read.

Note that lambda expressions can be used with any functional interface, not just Comparator. This makes them a powerful tool for writing concise and expressive code.

It’s worth noting that lambda expressions do have some limitations compared to traditional classes. For example, they cannot have instance variables or implement multiple interfaces. However, in many cases, lambda expressions can be a useful and powerful tool for simplifying your code.

Conclusion

In conclusion, the Comparator interface is a powerful tool for custom sorting of objects in Java. It allows for flexible sorting based on one or more fields of a custom object. We hope this tutorial has provided a comprehensive overview of the Comparator interface in Java and how it can be used to sort custom objects. By using the Comparator interface, you can write more efficient and effective code that can help you sort and organize data more easily.

Leave a Reply

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