When should I implement Comparator?

860 Views Asked by At

So I am learning about Comparator and Comparable and I have the following problem. I have a class:

public class PhoneBook implements Comparator<Name>{

    private SortedMap<Name, Integer> directory ;

    //class code constructor etc.

    //this is the method that the compiler wants if implementing Comparator
    @Override
    public int compare(Name o1, Name o2) {

        return o1.firstName.compareTo(o2.firstName);
      }
}

The other class, Name implements Comparable, and has two Strings first and last in the constructor. What I don't fully understand is the function of Comparator, I have read the Java documentation and I know it is used to sort elements differently without changing the Name class in my example it can also permit null values in some situations, but this declaration in my class constructor works fine, and I don't need to implement the Comparator Interface in the PhoneBook class at all:

Public PhoneBook(ArrayList<Name> names, ArrayList<Integer> phones) {
    this.directory = new TreeMap<Name, Integer>(new Comparator<Name>(){

        @Override
        public int compare(Name o1, Name o2) {

            return o1.firstName.compareTo(o2.firstName);
        }

     });
    //other constructor code to populate map
}

And achieves the functionality I want it to achieve without the need of implementing the Comparator interface by the PhoneBook class. My question is when might a class want to implement the Comparator interface? Is there a different way to have the map use a different sorting method(than the one provided by the Comparable interface in class Name) without passing it an anonymous class when initialising? I am sorry if this question is not clear enough, or not suitable for this web-site.

Edit: I understand the argument Comparable vs Comparator and when to use them. My question is more about how to use Comparator. Can you sort a Map without passing it a new Comparator when initialising? When is a good idea for a class to implement this interface?

1

There are 1 best solutions below

0
On BEST ANSWER

Classes that implement Comparator should not do anything else.

Since most such classes are only used in one place, it's very common to implement them unnamed, i.e. as an anonymous class like you did in the second example.

However, if you want the Comparator to be reusable, it'd be a good idea to create a standalone class for it, e.g. naming it FirstNameComparator in your example.

Note that in Java 8+, it's a lot easier to use a lambda expression instead of an anonymous class (since that is logically what a lambda expression becomes), and a method reference for reusable comparisons.

// Using anonymous class (Java 1.2+)
this.directory = new TreeMap<Name, Integer>(new Comparator<Name>() {
    @Override
    public int compare(Name n1, Name n2) {
        return n1.getFirstName().compareTo(n2.getFirstName());
    }
});
// Reusable named class (Java 1.2+)
public final class FirstNameComparator implements Comparator<Name> {
    @Override
    public int compare(Name n1, Name n2) {
        return n1.getFirstName().compareTo(n2.getFirstName());
    }
}

// Then use it like this:
this.directory = new TreeMap<Name, Integer>(new FirstNameComparator());
// Using lambda expression (Java 8+)
this.directory = new TreeMap<Name, Integer>(
    (n1, n2) -> n1.getFirstName().compareTo(n2.getFirstName())
);
// Using method reference (Java 8+)
public class PhoneBook {
    public PhoneBook(ArrayList<Name> names, ArrayList<Integer> phones) {
        this.directory = new TreeMap<Name, Integer>(PhoneBook::compareFirstName);
        // other constructor code
    }
    private static int compareFirstName(Name n1, Name n2) { // public, if reusable
        return n1.getFirstName().compareTo(n2.getFirstName());
    }
    // other PhoneBook code
}
// Using Comparator helper (Java 8+)
this.directory = new TreeMap<Name, Integer>(Comparator.comparing(Name::getFirstName));