1

Is there a way to create a class which implements Comparator and allows only a singleton instance to be created, or failing that only one instance per parameterized type?

I have tried a couple of mechanisms (both using a private constructor and then a getInstance method) but neither was valid syntax. So I'm wondering if type erasure means that it's impossible to rely on a single instance of a generic Comparator class.

The aim is to avoid having dozens of instances of the class, because all will behave identically and so a sole instance seems desirable. Is it possible? If not, what is the suggested way to avoid wild duplication of Comparator instances?

EDIT: This question seems to have caused confusion about what I'm trying to achieve, so I'll paste one of my broken attempts here. The code below is an attempt to create a Map which holds only a single instance of the Comparator for each type T. This is valid syntax, but I'm unable to call the getInstance method because I have no way to get a Class<T> instance in the client code.

final class IntervalComparator<T extends Comparable<T>> implements
        Comparator<Interval<T>> {

    // Private constructor. Use the getInstance method instead.
    private IntervalComparator() {
    }

    // Map to hold one instance for each type.
    private static Map<Class, IntervalComparator> cacheMap = new HashMap<>();

    // The only method which returns an instance of this Comparator.
    public static <K extends Comparable<K>> IntervalComparator<K>
            getInstance(Class<K> type) {
        IntervalComparator<K> soleInstance = cacheMap.get(type);
        if(soleInstance == null) {
            soleInstance = new IntervalComparator<>();
            cacheMap.put(type, soleInstance);
        }
        return soleInstance;
    }

    @Override
    public int compare(Interval<T> o1, Interval<T> o2) {
        // ... Comparison code goes here ...
    }
}

The client code looks like this:

private Set<Interval<T>> intervalSet;
private IntervalUnion(IntervalUnion.Builder<T> builder) {
    this.intervalSet = new TreeSet<>(new IntervalComparator<T>());
    // ... add Interval<T> objects into intervalSet ...
}

At the moment it is creating a new IntervalComparator<T> object each time it is called, and I don't think there's any way to pass Class<T> to the getInstance method.

Bobulous
  • 12,967
  • 4
  • 37
  • 68
  • How are you trying to invoke `getInstance()` method? Why you cannot pass `Class` instance? – Rohit Jain Feb 23 '14 at 17:19
  • Because the client code is also parameterized, so I don't have a concrete type to pass into the `getInstance` method. – Bobulous Feb 23 '14 at 17:21
  • @Arkanon Better to show that part of code too. If that class is parameterized, then you need to pass the Class instance somehow to that class too. It's hard to help without seeing the code. – Rohit Jain Feb 23 '14 at 17:22
  • I've added a sample from the client code which needs an instance of IntervalComparator in order to create a TreeMap. – Bobulous Feb 23 '14 at 17:28
  • @Arkanon Can't you pass also a `Class` instance to the `IntervalUnion` constructor? If not, is there any way you can get that from the `Builder`? There are only these two ways you can get `Class` instance. – Rohit Jain Feb 23 '14 at 17:31
  • I don't think there is a way. The Builder is perfectly entitled to contain null elements, so there's no way I know to guarantee that Class can be extracted from the Builder instance. – Bobulous Feb 23 '14 at 17:35
  • @Arkanon What do you mean by `null` elements? The instance is still there right? No matter there is `null` or not, but there sure is a type parameter associated with `Builder` right? Can you store a `Class` instance as field there. If not, you have to find some way to have that. – Rohit Jain Feb 23 '14 at 17:40
  • Is the comparison code different for each `T` of `Interval`? – Radiodef Feb 23 '14 at 17:52
  • @RohitJain Your suggestion of adding a field to the Builder which holds onto the Class did work, but it would have meant that I'd have to make similar changes to a chain of other classes which make up the package, and that seemed excessive just to suit the Comparator. I've now opted for the single instance and a warning-suppressed cast. – Bobulous Feb 23 '14 at 20:32

1 Answers1

1

I believe you can use one single comparator object for your purposes.

I assume that your comparator implementation IntervalComparator is stateless (in the sense that it has no fields to store anything). In this case you can always use the very same code to do the comparison.

final class IntervalComparator<T extends Comparable<T>> implements
    Comparator<Interval<T>> {

    // Private constructor. Use the getInstance method instead.
    private IntervalComparator() {
    }

    private static IntervalComparator INSTANCE = new IntervalComparator();

    @SuppressWarnings("unchecked")
    public static <K extends Comparable<K>> IntervalComparator<K> getInstance() {
        return (IntervalComparator<K>)INSTANCE;
    }

    @Override
    public int compare(Interval<T> o1, Interval<T> o2) {
        // ... Comparison code goes here ...
    }
}

Your use case would then be as follows:

private Set<Interval<T>> intervalSet;

private IntervalUnion() {
this.intervalSet = 
    new TreeSet<Interval<T>>(
      IntervalComparator.<T>getInstance());
// ... add Interval<T> objects into intervalSet ...
}

Note that there is an annotation @SuppressWarnings("unchecked") at the polymorphic getInstance() method. This is to surpress the compiler warning that the cast occurring in the method cannot be checked due to type erasure in Java generics.

By the way, there is a prominent persuasive precedent for such an implementation. The method java.util.Collections.emptyList() is similarly implemented:

@SuppressWarnings("unchecked")
public static final List EMPTY_LIST = new EmptyList<>();
// ...

@SuppressWarnings("unchecked")
public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}

(cf. http://www.docjar.com/html/api/java/util/Collections.java.html#3161)

Community
  • 1
  • 1
Matt
  • 868
  • 8
  • 12
  • That did the job (all 640 unit tests pass, so nothing broken by making the change you suggested). I did think it might come down to a single instance and a cast but I couldn't work out how to pass the parameterized type to the static method call. I've seen the `ClassName.methodName();` syntax before but I can't recall where. Do you have a link which describes this syntax? – Bobulous Feb 23 '14 at 20:28
  • 1
    The syntax and semantics of this construct is defined here: http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12, (pretty strong meat however). – Matt Feb 23 '14 at 20:37