compareTo() Method for Generic Pair Class?

Viewed 240

Good afternoon! I have written the following generic Pair class in Java, and was wondering if someone could help me with writing a compareTo() method for this class? I was hoping to have this method first compare the first elements of both objects, and only when they are equal does it compare the second elements.

public class Pair<T extends Comparable> {
protected T first;
protected T second;
public Pair() {
    first = second = null;
}
public Pair(T val1, T val2) {
    first = val1;
    second = val2;
}
public T getFirst() {
    return first;
}
public T getSecnd() {
    return second;
}
public void setFirst(T val) {
    first = val;
}
public void setSecond(T val) {
    second = val;
}
public String toString() {
    return "[" + first + "," + second + "]";
}
public boolean equals(Object other) {
    if(other == null)
        return false;
    else if (getClass() != other.getClass())
        return false;
    else {
        Pair<T> otherPair = (Pair<T>) other;
        return (first.equals(otherPair.first) && second.equals(otherPair.second));
    }
}
public static void main(String[] args) {
    Pair<Integer> location = new Pair<>(4, 6);
    Pair<String> team = new Pair<>("Steven", "Rebecca");
    System.out.println(location);
    System.out.println(team);
}

}

1 Answers

It gets a little complicated. In fact, the API as written cannot do that. In java, all objects neccessarily can be compared with equals / can be put in hashsets (in the sense that all objects have the hashCode() and equals() methods).. but they do not all have compareTo methods.

Ordering (sorting, TreeSets, etc) are fundamentally differently implemented. Unlike equality, just about every place where comparability is a thing, you can provide your own Comparator if you want. For example, you can either have a TreeSet (That's a set that keeps itself sorted) where the component type is Comparable (meaning: A class that defines a natural order. Not all classes do - only those that implement Comparable<Self>), or you must pass a Comparator<T> object along when you create it.

There is no constructor for e.g. HashSet where you can pass a custom 'Equalator' or whatnot, hence: They work differently.

You have to match that feature. Thus, either your Pair class must be initialized with a Comparator, or the generics need to be bounded so that the type of the Pair is at least Comparable, or, preferably, both.

Option 1:

class Pair<E> implements Comparable<Pair<E>> {
  final E first, second;
  final Comparator<? super E> comparator;

  public Pair(E first, E second, Comparator<? super E> comparator) {
    this.first = first;
    this.second = second;
    this.comparator = comparator;
  }

  @Override public int compareTo(Pair<E> other) {
    int c = comparator.compare(this.first, other.first);
    if (c != 0) return c;
    return comparator.compare(this.second, other.second);
  }
}

Second option:

class Pair<E extends Comparable<E>> implements Comparable<Pair<E>> {
  @Override public int compareTo(Pair<E> other) {
    int c = this.first.compareTo(other.first);
    if (c != 0) return c;
    return this.second.compare(other.second);
  }
}

The reason the first works is - you don't need how to order the 'first' of pair one against the 'first' of pair two.. but that comparator does, so you just ask it to do the comparison.

The reason the second works is - the lower bound of E is Comparable<E>, meaning, it has the compareTo(E other) method, so we just call it to do the job.

You can check the source code of e.g. java.util.TreeSet on how to do both - it requires some fancy footwork with generics on the constructor in order to set the comparator automatically (and it may require some 'generics hackery' where you tell javac to shut up about mismatched generics because you know it cannot fail even if the analysis is too complex for the compiler to do so it complains).

NB: More generally this Pair class is a bad idea. Java is very nominal - things should be named appropriately. Pair is anti-naming - Pair as a word says almost nothing. It works fine, it's just.. not very java-like. Also, your Pair requires both 'left' and 'right' to be the same type, that seems needlessly restrictive (you can have more than 1 type parameter!), and is mutable, which runs counter to most modern ideas about how to write this kind of library. The 2 fields should be private. Your code is also 'null confused'. It allows you to set first and second to null (no exception is thrown in setFirst if you try, and there's a no-args constructor), but your comparator will crash if you attempt to involve a Pair object where one or both of the components is null into anything that uses its comparable nature. That seems weird.

Related