cloooze
cloooze

Reputation: 173

When should I use Comparator vs Comparable?

I have a list of POJOs I need to sort somehow. I define a Comprator inside the POJO class and use it to sort the list.

Is the following way correct/best practice? Is there a better way to do it?

    public class CompratorTest {

    public static void main(String[] args) {

        List<Person> people = List.of(
                new Person("zoe", "saturday", 40),
                new Person("luca", "red", 15),
                new Person("boris", "vin", 54),
                new Person("boris", "apple", 33),
                new Person("boris", "apple", 70)
        );

        List<Person> sortedPeople =
                people.stream()
                        .sorted((person, other) -> Person.COMPARATOR.compare(person, other))
                        .collect(Collectors.toList());

        sortedPeople.forEach(System.out::println);
    }

    @Data
    @AllArgsConstructor
    static
    class Person {
        final static Comparator<Person> COMPARATOR =
                Comparator.comparing((Person person) -> person.getName())
                        .thenComparing(person -> person.getSurname())
                        .thenComparing(person -> person.getAge());

        String name;
        String surname;
        int age;
    }
}

Output is correct, by the way.

EDIT Adding a more classic way:

    @Data
    @AllArgsConstructor
    static class Animal implements Comparable<Animal> {

        String name;
        String race;

        @Override
        public int compareTo(Animal other) {
            if (this.name.equals(other.name)) {
                return String.CASE_INSENSITIVE_ORDER.compare(this.race, other.race);
            }

            return String.CASE_INSENSITIVE_ORDER.compare(this.name, other.name);
        }
    }

Which one do you think is a better solution?

Upvotes: 1

Views: 684

Answers (2)

Bruno Grieder
Bruno Grieder

Reputation: 29814

This is not opinion based: TL;DR implement Comparable:

  • semantically, this is what Interfaces are designed for: they express a contract enforced by an object, a behavior of the object: if the objects are serializable, then they should implement Serializable, if they are comparable, then they should implement Comparable, etc...

  • inheritance will work as expected and be more readable: if you define a Dog that extends Animal, you can implement comparison for Dog using the super implementation (i.e. a Dog is compared like any other Animal) or overriding the implementation to implement a behavior specific to Dog. The user of your Dog class simply calls instance.compareTo(...) without having to worry about what final static comparator she/he should call

  • users of your Animal API know they have to implement Comparable when adding their own animal to the inheritance tree

Upvotes: 0

Alexander Ivanchenko
Alexander Ivanchenko

Reputation: 28978

There's a substantial distinction between the use cases for Comparator and Comparable.

Implementing the Comparable interface is suitable for objects that have a natural order in your domain model. I'm not sure whether animals have a natural order, but if it is the case from the perspective of how your application model the animals, that's fine - that's the way to go. Otherwise, your class should not implement Comparable.

It's not something opinion-based, documentation clearly defines when these interfaces are intended to be used.

Comparable:

This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's compareTo method is referred to as its natural comparison method.

Comparator:

Comparators can also be used to control the order of certain data structures (such as sorted sets or sorted maps), or to provide an ordering for collections of objects that don't have a natural ordering.

Another obvious distinction, that you can define as many flavors of comparators as you need. Which is handy when there's no one specific way to compare and sort the objects. And they must have more meaningful names than comparator.

Personally, I don't see a huge harm in defining a couple of comparators as public static final fields, as in your example. If you have a single class that manages the instances of this type - extract the comparators into that class, otherwise if these objects are ubiquitous and used in many places you can leave them right inside the POJO (that an opinion based part).

Upvotes: 4

Related Questions