Объяснение подписи Collections.max

Я читал статью о Java Generics, когда я наткнулся на эту подпись метода:

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

Часть, которую я не получаю, заключается в том, что нам нужно иметь

Collection<? extends T> coll

не будет

Collection<T> coll

делать также?

Может кто-нибудь объяснить, почему следующая подпись недостаточно?

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

Заранее благодарим за ваши ответы. Это вызывает недоумение меня в течение довольно долгого времени.

Ответы

Ответ 1

Габор прав. Подстановочный знак позволяет статическому типу возвращаемого объекта отличаться от объявленного типа параметра введенной вами коллекции. Например, учитывая эти классы:

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;
    }
}

И этот класс:

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;
    }
}

Наблюдайте, какие вызовы компилируются и какие вызовы не выполняются:

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
}

Таким образом, подстановочный знак допускает некоторую гибкость. Хотя вы можете опустить подстановочный знак (и, честно говоря, я не могу думать о месте с верхней части головы, где требуется шаблон), есть причина, по которой он есть.


Том также неверен. Вы можете добавить null в коллекции с подстановочным знаком (если коллекция поддерживает add() в первую очередь):

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

И поскольку add(), remove() и большинство других мутаторов в интерфейсе Collection являются необязательными операциями, было бы безопасно мутировать коллекцию в любом случае с помощью этих методов, если параметр просто объявлен как Collection. Кроме того, обычно можно использовать iterator().remove() или что-то подобное для удаления элементов из коллекций независимо от того, были ли они объявлены с помощью шаблона, особенно для тех, которые уже включены в структуру коллекций Java.

Итак, в то время как подстановочный знак ограничивает то, что вы можете делать с коллекцией, он не должен использоваться как способ предотвращения изменений в коллекции.