Java: подстановочные знаки снова - программирование

Java: подстановочные знаки снова

В моем последнем question (спасибо вам всем, что мне ответил), я узнал разницу между List<Object> и List<?>.

Однако я до сих пор не вижу полезности подстановочных знаков.

У меня есть два ArrayList s:

ArrayList<Integer> li = new ArrayList<Integer>(Arrays.asList(1,2,3));
ArrayList<String> ls = new ArrayList<String>(Arrays.asList("one","two","three"));

Теперь рассмотрим два блока кода ниже:

static void printList(ArrayList<?> list) 
{
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

и

static <T> void printList(ArrayList<T> list) 
{
    for (T elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

Когда я звоню:

printList(li);
printList(ls);

Оба метода возвращают результат:

1 2 3
one two three

Однако второе решение, в цикле for, вместо Object, я использую параметризованные типы (гораздо более элегантные, я думаю).

Итак, главный вопрос остается: Зачем нам нужны подстановочные знаки?

4b9b3361

Ответ 1

Если вопрос заключается в "полезности подстановочных знаков":

Подстановочные знаки полезны, когда требуется только частичное знание параметра типа. "Частичное знание" реализуется по верхней и нижней границам (? Super T или? Extends T); если вы используете только несвязанный подстановочный знак (?), вы подразумеваете отсутствие знаний вообще, и вы не можете видеть, какие маскировки действительно полезны.

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

 class ListManager<T> {
    public void add(T item, List<? super T> list) {
        [... some useful operation ...]
         list.add(item);
    }
}

public class Main {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<Object>();
        Integer item = 10;
        ListManager<Integer> manager = new ListManager<Integer>();
        manager.add(item, list);
    }
}

Метод "ListManager.add()" создает связь между типом "списка" и типом "item". Операция над "списком" всегда безопасна по типу, но вы можете использовать метод "добавить" со списком разных типов параметров: мы использовали минимальное ограничение для параметра "список".

(см. также документацию jls7)

Ответ 2

В учебнике Java Generics говорится:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error

Так как мы не знаем, что означает тип элемента c, мы не можем добавлять к нему объекты. Метод add() принимает аргументы типа E, тип элемента коллекции. Когда фактический параметр типа есть?, Это означает какой-то неизвестный тип. Любой параметр, который мы передаем для добавления, должен быть подтипом этого неизвестного типа. Поскольку мы не знаем, что это такое, мы ничего не можем передать. Единственным исключением является null, который является членом каждого типа.

С другой стороны, учитывая List, мы можем вызвать get() и использовать результат. Тип результата - неизвестный тип, но мы всегда знаем, что это объект. Поэтому безопасно назначать результат get() переменной типа Object или передавать его как параметр, в котором ожидается тип объекта.

Таким образом, наличие подстановочного типа гарантирует, что мы не можем add() в List, за исключением null. Если вы просто вызываете get() в списке, вы знаете, что это хотя бы объект.

Ответ 3

Скажем, у вас есть эта иерархия классов:

Fruit
Apple extends Fruit
Orange extends Fruit

... и некоторые списки каждого типа плодов:

List<Apple> apples ; и List<Orange> oranges ;

Теперь вы хотите, чтобы List мог ссылаться на любой из этих списков.

Итак, вы объявляете List<? extends Fruit> fruits ;

Здесь вы не можете использовать переменную типа.

Ответ 5

Использование подстановочных знаков, ваш код является типичным. В

List myList;

вы можете добавить любой желаемый объект. Но в:

List<?> myList;

вы можете добавить только null.

Вам следует использовать List, без параметра type, когда вам нужен класс (List.class) или когда вам нужно проверить тип (экземпляр List).

Ответ 6

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

Однако я бы сказал, что это намного менее изящно, чем использование подстановочного знака. Переменная типа полезна для экспресс-установления ограничений между различными выражениями, например, когда есть два параметра с одной и той же переменной типа или между параметром и возвращаемым типом. Переменная типа, используемая только в одном месте в параметре, является пустой тратой - это именно то, что означает подстановочные знаки - переменная типа "анонимный", которая больше не нужна. Когда вам просто нужно выразить "какой-то тип", не требуя ограничения его каким-то другим, зачем загромождать вашу подпись метода с помощью переменных типа бонусов?

Существуют также другие применения подстановочных знаков, которые нельзя заменить переменными типа. Например, рассмотрим метод public List<?> foo(). Он возвращает какой-то список, но вы не знаете список того, что (и небезопасно добавлять что-либо к нему). Невозможно выразить это, используя переменные типа без подстановочных знаков.

Также рассмотрите подстановочные знаки в параметрах вложенного типа: вы можете иметь переменную типа List<List<?>>, то есть гетерогенный список, элементы которого могут быть списками разных вещей. Это также не может быть выражено без использования подстановочных знаков.

Ответ 7

Поскольку

вы можете изменить список в случае <T> list.add((T) new Object());

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

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error

Так как мы не знаем, что означает тип элемента c, мы не можем добавлять к нему объекты. Метод add() принимает аргументы типа E, тип элемента коллекции. Когда фактический параметр типа есть?, Это означает какой-то неизвестный тип. Любой параметр, который мы передаем для добавления, должен быть подтипом этого неизвестного типа. Поскольку мы не знаем, что это такое, мы ничего не можем передать. Единственным исключением является null, который является членом каждого типа.

С другой стороны, учитывая List<?>, мы можем вызвать get() и использовать результат. Тип результата - неизвестный тип, но мы всегда знаем, что это объект. Поэтому безопасно назначать результат get() переменной типа Object или передавать его как параметр, в котором ожидается тип Object

Дополнительная информация о WildCards

Ответ 8

Одним из лучших применений для подстановочных знаков, которые я нашел, являются сложные вложения общих типов.

Простой пример со списком Pair < T, E > , подстановочный код делает код короче и читабельнее, поскольку все могут видеть, что меня интересует только логическое значение.

  ArrayList<Pair<Boolean, OneOrOtherLongClassName>> pairList = ...;
  for(Pair<Boolean,?> p:pairList)
  {
      if(p.first)count++;
  }