Вступление
Привет!
Указание универсального типа позволяет Java выполнять проверку типа во время компиляции. Но при использовании универсальных типов в вашем коде из-за «стирания типа», которое происходит во время компиляции, параметры универсального типа преобразуются в тип Object
. Это делает общие параметры типа неспособными вызывать другие методы, кроме Object
.
Что, если мы хотим вызвать методы, отличные от тех, что в классе Object
? В этой статье объясняется, что такое «подстановочные знаки», «границы» и «стирание типа». И как использовать подстановочные знаки для повышения гибкости при использовании дженериков.
Тип Стирание
Википедия определяет стирание типа как:
Стирание типа - это процесс во время загрузки, при котором явные аннотации типов удаляются из программы перед ее выполнением во время выполнения.
В этом примере используются универсальные шаблоны, поэтому мы можем использовать его с несколькими типами.
class Container<T> { private T contents; public Container(T contents) { this.contents = contents; } public T getContents() { return contents; } public static void main(String[] args) { Container<String> container = new Container<>("Hello!"); String contents = container.getContents(); System.out.println(contents);// Hello! } }
Вы можете подумать, набирая Container<String> container = new Container<>(“Hello!”);
, что заменяете тип T
на String
. Но это не тот случай, во время компиляции компилятор Java удаляет параметр универсального типа T
из кода и заменяет его типом Object
:
class Container { private Object contents; public Container(Object contents) { this.contents = contents; } public Object getContents() { return contents; } public static void main(String[] args) { Container container = new Container("Hello!"); String contents = (String) container.getContents(); System.out.println(contents); } }
Обратите внимание, как String contents = container.getContents();
преобразуется в String contents = (String) container.getContents();
. Как вы можете видеть, компилятор вставил приведение при вызове метода getContents()
.
Универсальный тип подстановочного знака
Подстановочный знак, представленный ?
, полезен, когда вы хотите определить «любой тип».
Неограниченный подстановочный знак
Одно из общих ограничений - вы не можете, например, передать аргумент List ‹Integer› параметру List ‹Object›; это приведет к ошибке времени компиляции. На самом деле это хорошо, потому что Java защищает вас от вас!
public static void processList(List<Object> list) { for (Object o : list) System.out.print(o); } public static void main(String[] args) { List<Integer> numbers = List.of(1, 2); processList(numbers); // compile-time error }
Этот пример не работает, потому что List<Integer>
не является подтипом List<Object>
. Как и следовало ожидать, тип List<Object>
в параметре processList
необходимо заменить на List<?>
(читайте: список неизвестного типа)
Теперь я могу передать любой вид List в processList
метод:
public static void processList(List<?> list) { for (Object o : list) System.out.print(o); } public static void main(String[] args) { List<Integer> list = List.of(1, 2); processList(list);// 12 System.out.println(); List<String> strings = List.of("Hello", "World!"); processList(strings);// HelloWorld! }
ограниченный подстановочный знак
Использование ограниченных подстановочных знаков позволяет компилятору наложить ограничение на тип. Этот пример демонстрирует преимущества мощности и безопасности ограниченного подстановочного знака:
public static void transformList(List<? extends CharSequence> list){ List<Integer> charsCount = list.stream() .map(CharSequence::length) .collect(Collectors.toList()); System.out.println(charsCount); } public static void main(String[] args) { List<String> stringList = List.of("Hello", "World", "!"); transformList(stringList);// [5, 5, 1] List<StringBuilder> stringBuilders = List.of(new StringBuilder("Java"), new StringBuilder("Rocks")); transformList(stringBuilders);// [4, 5] }
Что делает transformList
, так это принимает List
и преобразует его в List<Integer>
, выражая длину каждого элемента.
Обратите внимание, что параметр transformList
равен List<? extends CharSequence>
, что означает, что этот метод будет принимать любой список с верхним ограничением до CharSequence
, например List<String>
, List<StringBuilder>
, любой список с подтипом CharSequence
может быть передан в качестве аргумента этому методу.
Заворачивать
В этом посте я попытался прояснить концепцию стирания типов и познакомил вас с подстановочными знаками и границами. Тем не менее, у дженериков есть чему поучиться. Если вам интересно и вы хотите узнать больше, я настоятельно рекомендую этот исчерпывающий FAQ по Java Generics.
Если вы нашли этот пост полезным, вы знаете, что теперь делать. Нажмите эту кнопку и подписывайтесь на меня, чтобы получать больше статей и руководств в своей ленте.