Разница между ? и Object в Java-генериках: HashMap
Быстрый ответ
Подстановочный символ ?
используется в дженериках для обозначения неизвестного типа, обеспечивая при этом гибкость кода, который работает с различными типами данных.
List<?> wildcardList; // Список, который может содержать объекты любого типа.
Object
— это корневой класс в иерархии Java. Использование его в дженериках означает, что список может содержать объекты любого типа, но без преимуществ типовой безопасности, который дженерики предлагают.
List<Object> objectList; // Список объектов без указания их конкретного типа.
Шаблоны с подстановочным символом ценятся в тех случаях, когда нужно обеспечить типовую инвариантность, в то время как использование Object
может быть менее выразительным при описании содержимого коллекции или цели операции.
Гибкость и типовая безопасность
Гибкость шаблонов
Символ ?
даёт возможность работать с коллекцией без уточнения типа её элементов. Например, HashMap<String, ?>
позволяет использовать значения различных типов.
Map<String, ?> map = new HashMap<>();
map.put("key1", "value1"); // Здесь добавляется строка...
map.put("key2", 10); // А тут — целое число!
Такой подход особенно полезен, когда важны только ключи коллекции или значения имеют разные типы.
Ограниченные шаблоны для типовой безопасности
Выражения ? extends T
и ? super T
позволяют задавать ограничения на типы в дженериках, усиливая при этом типовую безопасность. List<? extends InputStream>
примет только InputStream
и его подклассы.
List<? extends InputStream> streamList = new ArrayList<FileInputStream>();
В этом контексте операции со списком можно выполнять, зная, что он содержит только объекты InputStream
.
Принцип Получения и Размещения
Используйте ? extends T
для извлечения и ? super T
для добавления элементов в коллекцию, сохраняя чёткость намерений и типовую безопасность.
Разница между ковариантностью массивов и инвариантностью дженериков
Ковариантность массивов
Массивы в Java являются ковариантными. Это означает, что можно присвоить массив подкласса массиву суперкласса, но это может привести к ошибкам во время выполнения программы.
String[] strings = new String[1];
Object[] objects = strings; // Компилировать можно, но это небезопасно.
Инвариантность дженериков
В отличие от массивов, дженерики инвариантны: List<String>
не является подтипом List<Object>
, и попытка подобного приведения типов приведёт к ошибке компиляции.
Когда следует избегать шаблонов?
Запись в коллекцию
Избегайте использования подстановочных символов, если вам нужно читать из коллекции и записывать в неё данные. Это может нарушить типовую безопасность.
List<Integer> list = new ArrayList<>();
list.add(10); // Понятно и безопасно.
Методы с конкретными типами
Для методов лучше использовать конкретные обобщённые типы для сохранения типовой безопасности.
public <T> void processItems(List<T> items) {
// Обработка элементов указанного типа T.
}
Хранилище и Держатель: Каждому своё место
Хранилище для различных объектов
Collection<Object>
предназначена для хранения объектов любых типов, однако вы не сможете присвоить ей коллекцию конкретного типа, например, Collection<String>
.
Collection<Object> objects;
Collection<String> strings = new ArrayList<>();
// objects = strings; // Здесь возникнет ошибка компиляции.
Держатель любых коллекций
В отличие от Collection<Object>
, в Collection<?>
можно хранить коллекцию любого типа. Это демонстрирует применение полиморфизма.
Collection<?> holder;
Collection<String> strings = new ArrayList<>();
holder = strings; // Подходит для коллекций любого типа.
Расширяя возможности с ?
HashMap<?, ?>
дает свободу использования ключей и значений любых типов, что очень удобно, когда конкретные типы неизвестны или могут быть разнообразными.
Map<?, ?> myMap = ... ; // Подходит для пар "ключ-значение" любых типов.
Визуализация
Представьте себе дженерики в Java как набор инструментов, среди которых есть инструменты с конкретными обозначениями и универсальные, помеченные знаком вопроса.
| Тип инструмента | Описание в дженериках |
| --------------- | ------------------------- |
| С меткой | Object |
| Универсальный | ? (подстановочный символ) |
Object
в дженериках — это инструмент с конкретным назначением, в то время как ?
подходит для самых разнообразных задач.
Практическое применение ? и Object
Проектирование API для коллекций
Понимание различий между ?
и Object
важно для создания гибкого и адаптивного API.
public void printCollection(Collection<?> c) {
for (Object item : c) {
System.out.println(item); // Просто печатаем объекты.
}
}
Использование в Stream API
При работе со стримами в Java использование подстановочных символов позволяет обрабатывать элементы независимо от их конкретных типов.
Stream<?> mixedStream = Stream.of(1, "two", 3.0);
mixedStream.map(Object::toString).forEach(System.out::println); // Обработка и вывод каждого элемента.