Каран Ханчандани.
В 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 и Развлечение с подстановочными знаками. Вы можете проверить приведенные выше ссылки для получения дополнительной информации по теме.