Каран Ханчандани.

В Generics ?, называемый подстановочным знаком, представляет неизвестный тип. Мы можем видеть, как этот подстановочный знак используется в различных местах, таких как тип параметра, поле или локальная переменная, а иногда и как возвращаемый тип. Подстановочный знак никогда не следует использовать в качестве аргумента типа для вызова универсального метода, создания экземпляра универсального класса или супертипа.

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

Подстановочный знак с верхней границей

Вы можете использовать подстановочный знак с верхней границей, чтобы ослабить ограничения на переменную. Например, предположим, что вы хотите написать метод, который работает с List<Integer>, List<Double> и List<Number>; вы можете добиться этого, используя подстановочный знак с верхней границей.

Чтобы объявить подстановочный знак с верхней границей, используйте подстановочный знак ?, за которым следует ключевое слово extends, а затем его верхняя граница. Обратите внимание, что в этом контексте extends используется в общем смысле для обозначения либо «расширяет» (как в классах), либо «реализует» (как в интерфейсах).

Чтобы написать метод, который работает со списками Number и подтипов Number, таких как Integer, Double и Float, вы должны указать List<? extends Number>. Термин List<Number> является более строгим, чем List<? extends Number>, поскольку первый соответствует только списку типа Number, тогда как второй соответствует списку типа Number или любому из его подклассов.

Метод для приведенного выше случая будет выглядеть так

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

Подстановочный знак с нижней границей

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

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

Скажем, вы хотите написать метод, который помещает объекты Integer в список. Чтобы максимизировать гибкость, вы хотели бы, чтобы метод работал с List<Integer>, List<Number> и List<Object>, всем, что может содержать значения Integer.

Чтобы написать метод, работающий со списками Integer и надтипами Integer, такими как Integer, Number и Object, необходимо указать List<? super Integer>. Термин List<Integer> является более ограничительным, чем List<? super Integer>, поскольку первый соответствует только списку типа Integer, тогда как последний соответствует списку любого типа, являющегося супертипом Integer.

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

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

Примечание. Можно указать верхнюю границу для подстановочного знака или нижнюю границу, но нельзя указать и то, и другое.

Неограниченный подстановочный знак

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

Если вы пишете метод, который может быть реализован с использованием функций, предоставляемых в классе Object.

Когда код использует методы универсального класса, которые не зависят от параметра типа. Например, List.size или List.clear. На самом деле Class<?> так часто используется, потому что большинство методов в Class<T> не зависят от T.

Рассмотрим следующий метод printList:

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

Задача printList — напечатать список любого типа, но она не достигает этой цели — печатает только список из Object экземпляров; он не может печатать List<Integer>, List<String>, List<Double> и т. д., поскольку они не являются подтипами List<Object>. Чтобы написать общий метод printList, используйте List<?>:

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

Поскольку для любого конкретного типа A List<A> является подтипом List<?>, вы можете использовать printList для печати списка любого типа:

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

Важно отметить, что List<Object> и List<?> — это не одно и то же. Вы можете вставить Object или любой подтип Object в List<Object>. Но вы можете вставить null только в List<?>.

Рекомендации по использованию подстановочных знаков

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

Для целей этого обсуждения полезно думать о переменных как о выполняющих одну из двух функций:

1. Переменная «In» Переменная «in» передает данные коду. Представьте себе метод копирования с двумя аргументами: copy(src, dest). Аргумент src предоставляет данные для копирования, поэтому это параметр «in».

2. Переменная «Out» Переменная «out» содержит данные для использования в другом месте. В примере с копией copy(src, dest) аргумент dest принимает данные, поэтому это параметр «out».

Правила использования подстановочного знака

1. Переменная «in» определяется с помощью подстановочного знака с верхней границей, используя ключевое слово extends.

2. Переменная «out» определяется с помощью подстановочного знака с нижней границей, используя ключевое слово super.

3. В случае, когда к переменной «in» можно получить доступ с помощью методов, определенных в классе Object, используйте неограниченный подстановочный знак.

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

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

Список, определенный параметром List<? extends …>, можно неофициально рассматривать как доступный только для чтения, но это не является строгой гарантией. Предположим, у вас есть следующие два класса:

class NaturalNumber {    private int i;    public NaturalNumber(int i) { this.i = i; }
    // ...
}class EvenNumber extends NaturalNumber {    public EvenNumber(int i) { super(i); }
    // ...
}

Рассмотрим следующий код:

List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35));  // compile-time error

Поскольку List<EvenNumber> является подтипом List<? extends NaturalNumber>, вы можете присвоить le значение ln. Но вы не можете использовать ln для добавления натурального числа в список четных чисел. Возможны следующие операции в списке:

* Вы можете добавить null.

* Вы можете вызвать очистку.

* Вы можете получить итератор и вызвать удаление.

* Вы можете захватывать подстановочные знаки и записывать элементы, которые вы прочитали из списка.

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

Я надеюсь, что теперь вы можете легко использовать подстановочные знаки в своем коде. Я использовал примеры, показанные в ссылках Документация по Wildcards Oracle и Развлечение с подстановочными знаками. Вы можете проверить приведенные выше ссылки для получения дополнительной информации по теме.