В Java, что подстановочный знак может сделать то, что обычные дженерики не могут сделать?

Я новичок в Java. В этом документе они приводят это как пример использования подстановочного знака:

static void printCollection(Collection c) {
    Iterator i = c.iterator();
    for (int k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

Это их решение:

static void printCollection(Collection<?> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

Но я мог бы сделать то же самое без джокера:

static <T> void printCollection(Collection<T> c) {
    Iterator i = c.iterator();
    for (int k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

Может ли кто-нибудь показать мне простой случай использования, когда обычные генерики не будут работать, а подстановочный знак будет работать?

Обновление: ответы здесь Когда использовать шаблоны в Java Generics? НЕ говорите нам о необходимости подстановочного знака. На самом деле все наоборот.

3 ответа

Решение

Подстановочные знаки позволяют нам объявлять типы, не зависящие от конкретного параметра типа, например, "список любого вида":

List<List<?>> listOfAnyList = ...;

listOfAnyList.add( new ArrayList<String>() );
listOfAnyList.add( new ArrayList<Double>() );

Это невозможно без подстановочного знака:* потому что списки элементов могут иметь разные типы друг от друга.

И если мы попытаемся захватить это, мы обнаружим, что мы не можем:

static <E> void m(List<List<E>> listOfParticularList) {}

m( listOfAnyList ); // <- this won't compile

Еще одна вещь, подстановочные знаки позволяют нам делать то, что параметры типа не могут установить, это устанавливать нижнюю границу. (Параметр типа может быть объявлен с extends связаны, но не super оценка.**)

class Protector {
    private String secretMessage = "abc";

    void pass(Consumer<? super String> consumer) {
        consumer.accept( secretMessage );
    }
}

предполагать pass вместо этого было объявлено принять Consumer<String>, Теперь предположим, что у нас был Consumer<Object>:

class CollectorOfAnything implements Consumer<Object> {
    private List<Object> myCollection = new ArrayList<>();

    @Override
    public void accept(Object anything) {
        myCollection.add( anything );
    }
}

Проблема в том, что мы не можем передать это методу, принимающему Consumer<String>, декларирование Consumer<? super String> означает, что мы можем передать любой потребитель, который принимает String, (Также см. Java Generics: Что такое PECS?.)

В большинстве случаев подстановочные знаки позволяют нам делать аккуратные заявления.

Если нам не нужно использовать тип, нам не нужно объявлять параметр типа для него.


* Технически также возможно с необработанным типом, но необработанные типы не рекомендуется.

** Я не знаю, почему Java не позволяет super для параметра типа. 4.5.1. Аргументы типа параметризованных типов могут указывать на то, что это связано с ограничением вывода типов:

В отличие от переменных обычного типа, объявленных в сигнатуре метода, при использовании подстановочного знака не требуется никакого вывода типа. Следовательно, допустимо объявлять нижние границы подстановочным знаком […].

T обозначает универсальный тип этой структуры данных. В последнем примере вы не используете его, и он НЕ является фактическим типом (например, String), и поскольку вы его не используете, в данном случае это не имеет большого значения.

Например, если у вас была коллекция и вы пытались передать ее методу, который принимает коллекцию, это работает, потому что в пути к классам нет типа T, поэтому он считается переменной. Если вы попытались передать ту же коллекцию в метод, который принимает коллекцию, это не сработало бы, потому что у вас есть String в вашем пути к классам, поэтому он не является переменной.

Возьмите список в качестве примера.

  • List<?> может быть родительским классом List<A>,

например,

List<B> bList = new ArrayList<>(); // B is a class defined in advance
List<?> list = bList;

ты никогда не сможешь использовать <T> в этой ситуации.

  • <?> имеет подстановочный знак.

Вот,

  void foo(List<?> i) {
        i.set(0, i.get(0));
    }

код выше не может быть скомпилирован. Вы можете это исправить:

    void foo(List<?> i) {
        fooHelper(i);
    }

    // wildcard can be captured through type inference.
    private <T> void fooHelper(List<T> l) {
        l.set(0, l.get(0));
    }

узнать больше, http://docs.oracle.com/javase/tutorial/java/generics/capture.html

Я могу думать только о двух в настоящее время, позже может обновить.

Другие вопросы по тегам