6

I have the following class.

class MyClass<T>

It uses the following constructor.

MyClass(Comparator<T> comparator, Collection<? extends T> data)

And it has a field which is set in the constructor like so:

this.data = Collections.unmodifiableCollection(data);

In the special case where T implements Comparable, I don't want to require that a comparator be passed in, since I can just use the natural ordering. So I thought I should be able to use this constructor:

public <T extends Comparable<T>> MyClass(Collection<T> data)

But there is apparently a type mismatch: cannot convert from Collection<T> to Collection<? extends T> in the assignment statement above. I've tried all sorts of things: adding more generic parameters, and so on, but none work. I seem unable to specify a bound that says: if you have a type T that implements Comparable, do the straightforward thing.

Any ideas?

Thanks.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
dgorur
  • 1,638
  • 4
  • 16
  • 26
  • I gather that you want to declare it as a second constructor, right? So changing `MyClass` to `MyClass>` is not an option? – BalusC Oct 25 '11 at 00:16
  • I find that I can do this using a static factory method: static > MyClass getInstance(Collection extends V> data), but this is somewhat unsatisfying. – dgorur Oct 25 '11 at 00:24
  • (Note the last code example creates two generic parameters both called `T` - one on the class and one on the constructor (like on a method, only very rarely useful). – Tom Hawtin - tackline Oct 25 '11 at 00:36

3 Answers3

3

Unfortunately I don't think this kind of "if Comparable do this else do that" logic is possible with the Java type system.

You could split the Comparable and non-Comparable cases into separate classes and hide them behind an interface, something like this:

interface Interface<T> {
    public void processData();
}

class MyClass<T> implements Interface<T> {
    private final Collection<? extends T> data;
    MyClass(Comparator<T> comparator, Collection<? extends T> data) {
        this.data = data;
    }
    public void processData() {
        // ...
    }
}

class MyCompClass<T extends Comparable<T>> implements Interface<T> {
    private final Collection<? extends T> data;
    MyCompClass(Collection<? extends T> data) {
        this.data = data;
    }
    public void processData() {
        // ...
    }
}

class Factory {
    static <T extends Comparable<T>> Interface<T> create(Collection<? extends T> data) {
        return new MyCompClass<T>(data);
    }
    static <T> Interface<T> create(Comparator<T> comparator, Collection<? extends T> data) {
        return new MyClass<T>(comparator, data);
    }
}

But this might result in a lot of duplicated code. Another option is to leave MyClass requiring a Comparator in its constructor, and build that comparator in the factory:

class MyClass<T> {
    private final Collection<? extends T> data;
    MyClass(Comparator<T> comparator, Collection<? extends T> data) {
        this.data = data;
    }
    public void processData() {
        // ...
    }
}

class Factory {
    static <T extends Comparable<T>> MyClass<T> create(Collection<? extends T> data) {
        return new MyClass<T>(new Comparator<T>() {
            public int compare(T o1, T o2) {
                return o1.compareTo(o2);
            }
        }, data);
    }
    static <T> MyClass<T> create(Comparator<T> comparator, Collection<? extends T> data) {
        return new MyClass<T>(comparator, data);
    }
}
Chris B
  • 9,149
  • 4
  • 32
  • 38
  • Your second solution is just what I've ended up doing. Thanks for clarifying that what I wanted to do is indeed not possible with Java's type system. – dgorur Oct 25 '11 at 00:31
  • you can even put that factory method inside `MyClass` – newacct Oct 25 '11 at 20:27
0

I concur this appears to be impossible, as the straightforward solution

class MyClass<T> {

    Collection<? extends T> data;

    public MyClass(Comparator<T> comparator, Collection<? extends T> data) {
        this.data = data;
    }

    public <E extends T & Comparable<T>> MyClass(Collection<E> data) {
        this.data = data;
    }

}

is rejected by the compiler with

Cannot specify any additional bound Comparable<T> when first bound is a type parameter

See also Why can't I use a type argument in a type parameter with multiple bounds? (in particular Chris Povirk's answer).

As for solutions, I agree with Chris B.

Community
  • 1
  • 1
meriton
  • 68,356
  • 14
  • 108
  • 175
0

You can use the regular constructor for the first case and a factory method for the second one:

class MyClass<T> {

    Collection<? extends T> data;
    Comparator<? super T> comparator;

    public MyClass(Comparator<? super T> comparator, Collection<? extends T> data) {
        this.data = data;
        this.comparator = comparator;
    }

    public static <T extends Comparable<? super T>> MyClass<T> fromComparable(Collection<T> data) {
        return new MyClass<T>(Collections.reverseOrder(Collections.reverseOrder()), data);
    }

}
newacct
  • 119,665
  • 29
  • 163
  • 224