14

I was reading an article on Java Generics when I stumbled on this method signature:

static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll);

The part that I don't get is why we need to have

Collection<? extends T> coll

wouldn't

Collection<T> coll

do as well?

Could someone please explain why the following signature is not adequate?

static <T extends Object & Comparable<? super T>> T max(Collection<T> coll);

Thanks in advance for your replies. This keeps puzzling me for quite some time now..

meriton
  • 68,356
  • 14
  • 108
  • 175
Harry
  • 415
  • 3
  • 7
  • 4
    For example you want to return a `Number`, but you also want to work with either `Double` or `Integer` collections too. This way you can specify at call site `Number`: `max(doubles)`. – Gábor Bakos Dec 15 '14 at 13:05
  • thanks for your reply. However, I think that would still work if we have Collection as an argument. – Harry Dec 15 '14 at 13:12
  • 1
    My suspicion is that the only difference it makes in that context to use Collection extends T> coll instead of Collection coll, is that with the former we limit the operations that can be performed on the list (e.g. adding a value of type T) – Harry Dec 15 '14 at 13:14
  • 3
    Using the wildcard prevents the method from inadvertently adding to or otherwise mutating the collection, since a *consumer* would need to require *super* (always remember the PECS mnemonic!) – Thorn G Dec 15 '14 at 13:20
  • Thus, I think Tom's reply verifies my suspucion – Harry Dec 15 '14 at 13:26
  • 1
    you can also see it follows the PECS principle. Coll is a provider of T, so it uses extends – AdrianS Dec 15 '14 at 13:53

1 Answers1

12

Gábor is correct. The wildcard allows the static type of the returned object to differ from the declared parameter type of the collection you input. For example, given these classes:

interface S extends Comparable<S> {}
class A implements S {
    @Override
    public int compareTo(final @NotNull S o) {
        return 0;
    }
}
class B implements S {
    @Override
    public int compareTo(final @NotNull S o) {
        return 0;
    }
}

And this class:

class Test {

    @Nullable
    static <T extends Comparable<? super T>> T extendsMax(
            Collection<? extends T> coll) {
        return null;
    }

    @Nullable
    static <T extends Comparable<? super T>> T max(Collection<T> coll) {
        return null;
    }
}

Observe what calls compile and what calls do not:

public static void main(String[] args) {
    final Collection<S> sColl = new ArrayList<>();
    final Collection<A> aColl = new ArrayList<>();
    final Collection<B> bColl = new ArrayList<>();

    final S s1 = Test.<S> extendsMax(sColl); // compiles, T = S, <? extends T> = S
    final S s2 = Test.<S> extendsMax(aColl); // compiles, T = S, <? extends T> = A
    final S s3 = Test.<S> extendsMax(bColl); // compiles, T = S, <? extends T> = B
    final A a1 = Test.<A> extendsMax(aColl); // compiles, T = A
    final B b1 = Test.<B> extendsMax(bColl); // compiles, T = B

    final S s4 = Test.<S> max(sColl); // compiles, T = S
    final S s5 = Test.<S> max(aColl); // does not compile, T = S, T != A
    final S s6 = Test.<S> max(bColl); // does not compile, T = S, T != B

    final S s7 = Test.max(aColl); // compiles, but because T = A, and A 
                                  // can be assigned to S
}

So the wildcard allows for some flexibility. While you can omit the wildcard (and to be honest, I can't think of a place off the top of my head where the wildcard is required), there is a reason it is there.


Tom is also incorrect. You can add null to collections with a wildcard (if the collection supports add() in the first place):

List<? extends Number> list = new ArrayList<>();
list.add(null); // compiles, and should execute just fine

And because add(), remove(), and most other mutators in the Collection interface are optional operations, it wouldn't be safe to mutate the collection anyways through those methods if the parameter is just declared as a Collection. In addition, it's generally possible to use iterator().remove() or something of the sort to remove elements from collections regardless of whether they were declared with a wildcard, especially for the ones already included in the Java Collections Framework.

So while a wildcard does limit what you can do with a collection, it should not be used as a way to prevent changes to a collection.

awksp
  • 11,764
  • 4
  • 37
  • 44
  • `Test. max(aColl); // does not compile` of course it does not. `T = S`, and `aColl` is `Collection`. But as you mentioned later, you don't need the `` to explicit `T` – njzk2 Dec 15 '14 at 16:16
  • @njzk2 The point was that the wildcard-less `max()` call would work in this situation, but not for the reason that one might expect. – awksp Dec 15 '14 at 16:17
  • 1
    true. `T` would be `A`, because of `aColl`, and not `S` because of the assignment. – njzk2 Dec 15 '14 at 16:18
  • I still don't get the point "The wildcard allows the static type of the returned object to differ from the declared parameter type of the collection you input." In your example you've proved that the code doesn't compile when explicitly using ., but how does the extends T> help in the returned type to differ? – Harry Dec 15 '14 at 16:44
  • I'd appreciate it though if someone can post a scenario where the wildcard version is actually required – Harry Dec 15 '14 at 16:58
  • 1
    Thanks @user3580294. It is represented in a much cleaner way. And I also forgot about the `remove` method for mutating. – Gábor Bakos Dec 15 '14 at 18:37
  • 1
    @GáborBakos `Collections.swap(…)` or `Collections.sort(…)` also work with wildcard types. There’s also `clear()` to remove all elements. – Holger Mar 18 '21 at 08:25
  • @Holger you are right, those can also mutate. (Of course I also wrap things in unmodifiable collections when I need non-modifiability, my habit is just for the easier tracking of this.) – Gábor Bakos Mar 18 '21 at 12:15