Implementing Interface Comparators

Let's say I have a simple interface that I want to be Comparable based on some function:

 interface Organism extends Comparable<Organism> { String getName(); int getComplexity(); @Override default int compareTo(Organism other) { return this.getComplexity() - other.getComplexity(); } } 

Each implementation class must return unique complexity, so any two instances of the class will have the same complexity, and any two instances of the other class will have different complexity. The natural order will "group" all instances of classes together.

Now I want to implement this interface in a class that overrides the default comparison, specifically designed to compare two instances of this class within this group of instance classes in order. I am using the following pattern:

 class Bacteria implements Organism { enum Shape {ROD, ROUND, SPIRAL}; private final Shape shape; @Override public int compareTo(Organism other) { if (other instanceof Bacteria) return this.shape.compareTo((Bacteria)other.shape); else return Organism.super.compareTo(other); } } 

I am not very happy with this code scheme: when the set of classes implementing the interface becomes large, it becomes quite difficult to maintain and requires a lot of repetitive code and depends on the implicit property of "complexity". I prefer the Comparator style to determine the order. I would like to be able to implement Comparable in Bacteria using something similar:

 return Comparator .comparingInt(Organism::getComplexity) .thenComparing(Bacteria::getShape); 

To be clear, I understand that comparators do not work like this: they are designed in such a way that one comparator is used in the collection, and not another comparator depending on each object. I mention them here not because they are a potential solution, but because the coupling style of the comparators is elegant and transparent. I am wondering if there is an equally elegant way to define compareTo to allow different ordering within the collection depending on the class.

+7
java comparison
source share
2 answers

I'm not sure where you plan to put Comparator . Since you want your class to implement Comparable<Organism> , I assume you are looking for something like

 class Bacteria implements Organism { enum Shape {ROD, ROUND, SPIRAL}; private final Shape shape; Comparator<Organism> comparator = Comparator .comparingInt(Organism::getComplexity) .thenComparing(Bacteria::shape); // illegal @Override public int compareTo(Organism other) { return comparator.compare(this, other); } } 

This will not work, because thenComparing , in the context, needs a Function parameter that works with Organism , not Bacteria . You can get around this this way: I will leave it to you to decide if it is elegant enough:

 Comparator<Organism> comparator = Comparator .comparingInt(Organism::getComplexity) .thenComparing(x -> ((x instanceof Bacteria) ? ((Bacteria)x).getShape() : Shape.ROD)); 

In theory, you can also write your own method, which can convert a comparator to another. You cannot use the notation instance.method , so the use should be something like this:

 Comparator<Organism> comparator = MyComparatorUtilities.thenComparingIfInstanceOf( Comparator.comparingInt(Organism::getComplexity), Bacteria.class, Bacteria::getShape); 

The following implementation of thenComparingIfInstanceOf compiles (and allows you to compile the code above), but I have not tried to test it:

 class MyComparatorUtilities { public static <T,U extends T,V extends Comparable<? super V>> Comparator<T> thenComparingIfInstanceOf( Comparator<T> comparator, Class<U> subclass, Function<? super U, ? extends V> keyExtractor) { return (a, b) -> { int comp = comparator.compare(a, b); if (comp != 0) { return comp; } if (subclass.isInstance(a) && subclass.isInstance(b)) { return keyExtractor.apply(subclass.cast(a)) .compareTo(keyExtractor.apply(subclass.cast(b))); } return 0; }; } } 

MORE : To answer the comment: no, I do not necessarily think that this approach is more readable or supported. In fact, I believe that the whole project is unattainable because it is too easy to add a class that will cause the comparison to violate the properties of full ordering; I would look for a different design, starting with a clearer definition of how I want the order to work on objects of different classes. The β€œright” way to handle comparisons is likely to depend on this other design.

For such problems, I can take the compareTo approach, unless I need a class to return Comparator for some other reason (for example, for Organism s) there are several orders. However, I could look for ways to eliminate duplication if each compareTo is an if with the same structure or something like that.

0
source share

You can do something like:

 return Comparator .comparingInt(Organism::getComplexity) .thenComparing(o-> o instanceof Bacteria ? ((Bacteria) o).getShape() : Bacteria.Shape.ROD); 

So, if it is Bacteria, then it compares the figures, otherwise it compares a constant of the same type, which is always equal. If you do not use the same type in comparison, it will not compile.

This is not a common interface, but it may help others who are trying to compare the properties of a limited number of subclasses.

0
source share

All Articles