Подтвердить что ты не робот

Когда использовать подстановочные знаки в Java Generics?

это от HeadFirst Java: (стр. 575)

Это:

public <T extends Animal> void takeThing(ArrayList<T> list)

Делает то же самое, что и это:

public void takeThing(ArrayList<? extends Animal> list)

Итак, вот мой вопрос: если они точно такие же, почему мы не пишем

public <? extends Animal> void takeThing(ArrayList<?> list)

или

public void takeThing(ArrayList<T extends Animal> list)

Также, когда было бы полезно использовать? вместо T в объявлении метода (как указано выше) с Generics или для объявления класса? Каковы преимущества?

4b9b3361

Ответ 1

Большая разница между

public <T extends Animal> void takeThing(ArrayList<T> list)

и

public void takeThing(ArrayList<? extends Animal> list)

заключается в том, что в первом методе вы можете ссылаться на "T" внутри метода как на конкретный класс, который был дан. Во втором методе вы не можете этого сделать.

Вот более сложный пример, чтобы проиллюстрировать это:

// here i can return the concrete type that was passed in
public <T extends Animal> Map<T, String> getNamesMap(ArrayList<T> list) {
    Map<T, String> names = new HashMap<T, String>();
    for (T animal : list) {
        names.put(animal, animal.getName()); // i assume there is a getName method
    }
    return names;
}

// here i have to use general Animal
public Map<Animal, String> getNamesMap(ArrayList<? extends Animal> list) {
    Map<Animal, String> names = new HashMap<Animal, String>();
    for (Animal animal : list) {
        names.put(animal, animal.getName()); // i assume there is a getName method
    }
    return names;
}

С помощью первого метода, если вы перейдете в список кошек, вы получите ключ "Карта с кошкой". Второй метод всегда будет возвращать карту с общим ключом Animal.

Кстати, это недопустимый синтаксис java:

public <? extends Animal> void takeThing(ArrayList<?> list)

Используя эту форму объявления универсального метода, вы должны использовать действительный java-идентификатор, а не "?".

Edit:

Форма "? extends Type" применяется только к объявлению переменной или типа параметра. В рамках описания общего метода он должен быть "Идентификатор расширяет тип", поскольку вы можете ссылаться на "Идентификатор" из вашего метода.

Ответ 2

Дикие карты - это совместное/противоположное отклонение дженериков. Я попытаюсь прояснить, что это означает, предоставив несколько примеров.

В основном это связано с тем, что для типов S и T, где S является подтипом T, общий тип G<S> не является допустимым подтипом G<T>

List<Number> someNumbers = new ArrayList<Long>(); // compile error

Вы можете исправить это с помощью диких карт

List<? extends Number> someNumbers = new ArrayList<Long>(); // this works

Обратите внимание, что вы не можете помещать что-либо в такой список

someNumbers.add(2L); //compile error

четный (и более удивительный для многих разработчиков):

List<? extends Long> someLongs = new ArrayList<Long>();
someLongs.add(2L); // compile error !!!

Я думаю, что это не подходящее место, чтобы обсудить это подробно. Я попытаюсь найти некоторые статьи и статьи, которые объясняют это более подробно.

Ответ 3

Связывание типа с параметром типа может быть более мощным, в зависимости от того, что должен делать метод. Я не уверен, что takeThing должен делать, но представьте, в общем, у нас есть метод с одной из этих сигнатур типа:

public <T extends Animal> void foo(ArrayList<T> list);

//or

public void foo(ArrayList<? extends Animal> list);

Вот конкретный пример того, что вы можете сделать только с типом first:

public <T extends Animal> void foo(ArrayList<T> list) {
    list.add(list.remove(0)); // (cycle front element to the back)
} 

В этом случае T требуется сообщить проверяющему тип, что элемент, удаляемый из списка, является элементом ОК, который нужно добавить в список.

Вы не могли бы сделать это с помощью подстановочного знака, потому что, поскольку подстановочный знак не привязан к параметру типа, его контекст не отслеживается (ну, он отслеживается, через "захватывает", но недоступен для использования). Вы можете получить дополнительную информацию об этом в другом ответе, который я дал: Как работают дженерики дженериков?

Ответ 4

Если вы пишете ? extends T, вы говорите "все, что является Т или более конкретным". Например: a List<Shape> может содержать только Shape, а List<? extends Shape> может иметь Shape s, Circle s, Rectangle s и т.д.

Если вы пишете ? super T, вы говорите "все, что является Т или более общим". Это реже используется, но имеет прецеденты. Типичным примером может быть обратный вызов: если вы хотите передать Rectangle обратно на обратный вызов, вы можете использовать Callback<? super Rectangle>, так как Callback<Shape> также сможет обрабатывать Rectangle.

Здесь соответствующая статья Википедии.

Ответ 5

Если вашему методу takeThing необходимо добавить элементы в параметр list, версия подстановочного знака не будет компилироваться.

Интересный случай - когда вы не добавляете в список, и обе версии, похоже, компилируются и работают.

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

Например, java.util.Collection объявляет:

interface Collection<E> {
  ...
  public boolean containsAll(Collection<?> c);
  ...
}

И предположим, что у вас есть следующий код:

Collection<Object> c = Arrays.<Object>asList(1, 2); 
Collection<Integer> i = Arrays.<Integer>asList(1, 2, 3); 
i.containsAll(c); //compiles and return true as expected

Если java.util.Collection будет:

interface Collection<E> {
    ...
    public boolean containsAll(Collection<E> c);
    ...
}

Вышеуказанный тестовый код не будет компилироваться, и гибкость API Collection будет уменьшена.

Стоит отметить, что последнее определение containsAll имеет преимущество ловить больше ошибок во время компиляции, например:

Collection<String> c = Arrays.asList("1", "2"); 
Collection<Integer> i = Arrays.asList(1, 2, 3); 
i.containsAll(c); //does not compile, the integer collection can't contain strings

Но пропускает действительный тест с помощью Collection<Object> c = Arrays.<Object>asList(1, 2);

Ответ 6

Использование универсальных шаблонов Java Generic определяется принципом GET-PUT (который также известен как принцип IN-OUT). Это говорит о том, что: Используйте подстановочный символ "extends", когда вы получаете только значения из структуры. Используйте "супер", когда вы добавляете значения в структуру и не используете подстановочные знаки, когда вы делаете оба. не относятся к типу возвращаемого метода. Не используйте подстановочный знак в качестве возвращаемого типа. См. Пример ниже:

public static<T> void copyContainerDataValues(Container<? extends T> source, Container<? super T> destinationtion){
destination.put(source.get());
}