When Should a Class Be Comparable And/Or Comparator

When should a class be Comparable and/or Comparator?

The text below comes from Comparator vs Comparable

Comparable

A comparable object is capable of comparing itself with another object. The class itself must implements the java.lang.Comparable interface in order to be able to compare its instances.

Comparator

A comparator object is capable of comparing two different objects. The class is not comparing its instances, but some other class’s instances. This comparator class must implement the java.util.Comparator interface.

When to use Comparable and Comparator

I would say that an object should implement Comparable if that is the clear natural way to sort the class, and anyone would need to sort the class would generally want to do it that way.

If, however, the sorting was an unusual use of the class, or the sorting only makes sense for a specific use case, then a Comparator is a better option.

Put another way, given the class name, is it clear how a comparable would sort, or do you have to resort to reading the javadoc? If it is the latter, odds are every future sorting use case would require a comparator, at which point the implementation of comparable may slow down users of the class, not speed them up.

When should I use Comparator vs Comparable?

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).

Java : Comparable vs Comparator

When your class implements Comparable, the compareTo method of the class is defining the "natural" ordering of that object. That method is contractually obligated (though not demanded) to be in line with other methods on that object, such as a 0 should always be returned for objects when the .equals() comparisons return true.

A Comparator is its own definition of how to compare two objects, and can be used to compare objects in a way that might not align with the natural ordering.

For example, Strings are generally compared alphabetically. Thus the "a".compareTo("b") would use alphabetical comparisons. If you wanted to compare Strings on length, you would need to write a custom comparator.

In short, there isn't much difference. They are both ends to similar means. In general implement comparable for natural order, (natural order definition is obviously open to interpretation), and write a comparator for other sorting or comparison needs.

Difference between Comparable and Comparator?

Not really.

Comparable and Comparator are generic interfaces that allow to compare instances of the type defined in the generic (subclasses included).

The main difference between them is that Comparable is directly implemented in the class which you want to compare objects.

Consequently, if you have a single way to compare instances from a class, that is that you have a natural order for them, Comparable is the right approach.

On the other hand, if you have multiple ways to compare instances from a class, Comparable is not enough.

You should use Comparators instead (if it doesn't exist a natural order) or use both (if it exists a natural order and some other kinds of order).


Example where Comparator can be useful in addition to Comparable :

The String class implements Comparable by comparing two strings lexicographically.
Suppose you need to sort a List of String according to a different rule : their length.

You will need to define a Comparator<String> that implements this rule such as :

public class StringLengthComparator implements Comparator<String> {

@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}

}

Now you could sort Strings by using their natural order (using Comparable) :

List<String> strings = new ArrayList<>();
...
strings.sort();

But you could also use a specific Comparator<String> :

strings.sort(new StringLengthComparator());

Or without creating any class with a lambda:

strings.sort((o1,o2)->Integer.compare(o1.length(), o2.length()));

Example where Comparator should be used instead of Comparable :

Suppose you have an Account class that represents a bank account.

Functionally you don't have a natural order to sort them but you have instead multiple orders according to the client needs.
Making the class to implement Comparable would not make sense. But creating distinct Comparator<Account> would.


Case where only Comparator can be used :

If you want to define a order for instances of a class which you cannot change the source code (JDK class or third party class), Comparator is the way to follow.

Java: Comparable vs Comparator - Memory & Performance

That answer is incorrect.

Adding implemented interfaces or methods does not influence the memory required for individual instances of a class.

First of all, conceptually it doesn't make sense.

Implemented interfaces and methods are per-class information. Two instances of the same class will always implement exactly the same interfaces and have the exact same methods. As such, it makes no sense for the JVM to store that information per-object.

Second, you can easily verify that with sample code like this:

public class MyClass implements Comparable<MyClass> {

private final long l;

MyClass(long l) {this.l = l;}

@Override
public int compareTo(MyClass o) {
return 0;
}

public static void main(String[] args) {
long l = 0;
try {
var list = new ArrayList<MyClass>();
while (true) {
list.add(new MyClass(l++));
}
} catch (OutOfMemoryError e) {
System.out.println("Created " + l + " objects before things went south ...");
}
}
}

Running this with -Xmx32m using Java 11 will create about 200000 objects on each run for me (with slight variations, probably due to GC details).

Removing the Comparable interface and/or the compareTo method does not significantly change that value.

You can try adding additional fields or removing l, which will change the number.

When should I implement Comparator?

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));

Why use comparable and comparator interface

A question such as "why use Comparator or Comparable rather than bubble sort?" really makes no sense. These are not alternatives to each other. Any sorting algorithm (including ones that you code yourself) needs a mechanism for defining the order of the objects. That is what the interfaces do: provide a mechanism for defining an ordering on objects.

If the question is "why use sorting methods built into the JDK rather than write your own?" then the answer is probably fairly obvious: it saves effort and complexity.

If the question is "when should you use Comparable and when should you use Comparator?" then I would suggest searching for one of the excellent primers on this topic. As a very simplified answer, Comparable is used to define a natural (default) ordering for objects within a class while Comparator is used to define a custom ordering to be passed to a method.

Note also that the Comparator interface has many very powerful methods for defining comparisons. Very little needs to be done manually now. For example, if you have a Person class you can sort a list with code such as:

Collection.sort(personList, Comparator
.comparingInt(Person::getAge)
.thenComparing(Person::getSurname));

In my view this code is better than the old mechanism of defining compareTo methods in a class as it hides away the implementation details of returning arbitrary integers representing comparison between fields and makes the intention pretty obvious. In fact it's enough of an improvement on older methods that in my own code I tend to avoid natural orderings altogether unless the class has an inherent obvious order that can be naturally represented as the difference between two integers (e.g. a class defining temperature or heights).

Should I use Comparator or Comparable when trying to use a Priority Queue with this generic class?

As you already have implemented compareTo, you have Comparable Flight or Event instances.

That means you're set to use it with Comparable objects. All of the below should work:

Queue<Event> eventQueue = new PriorityQueue<>();
eventQueue.add(new Flight(scheduledTime, eventType, identifier));

Or:

List<Flight> flightList = Arrays.asList(new Flight(scheduledTime, 
eventType, identifier));
Queue<Flight> flightQueue = new PriorityQueue<>(flightList);

Or:

List<Event> eventList = ...;
Queue<Event> eventQueue = new PriorityQueue<>(eventList);

The PiorityQueue class should be able to handle the priority according to the order dictated by your compareTo ordering.

Note: if your List<Event> has objects of other classes that implement Event, then you must make sure that those other classes also have compareTo(Event otherFlight). Otherwise, the priority queue may raise exceptions at runtime.

The best option may be to just declare Flight as implementing Comparable<Flight> and instantiate a PriorityQueue<Flight> queue.



Related Topics



Leave a reply



Submit