Разница между ? и Object в Java-генериках: HashMap

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

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

Java
Скопировать код
List<?> wildcardList; // Список, который может содержать объекты любого типа.

Object — это корневой класс в иерархии Java. Использование его в дженериках означает, что список может содержать объекты любого типа, но без преимуществ типовой безопасности, который дженерики предлагают.

Java
Скопировать код
List<Object> objectList; // Список объектов без указания их конкретного типа.

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

Кинга Идем в IT: пошаговый план для смены профессии

Гибкость и типовая безопасность

Гибкость шаблонов

Символ ? даёт возможность работать с коллекцией без уточнения типа её элементов. Например, HashMap<String, ?> позволяет использовать значения различных типов.

Java
Скопировать код
Map<String, ?> map = new HashMap<>();
map.put("key1", "value1");      // Здесь добавляется строка...
map.put("key2", 10);            // А тут — целое число!

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

Ограниченные шаблоны для типовой безопасности

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

Java
Скопировать код
List<? extends InputStream> streamList = new ArrayList<FileInputStream>();

В этом контексте операции со списком можно выполнять, зная, что он содержит только объекты InputStream.

Принцип Получения и Размещения

Используйте ? extends T для извлечения и ? super T для добавления элементов в коллекцию, сохраняя чёткость намерений и типовую безопасность.

Разница между ковариантностью массивов и инвариантностью дженериков

Ковариантность массивов

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

Java
Скопировать код
String[] strings = new String[1];
Object[] objects = strings; // Компилировать можно, но это небезопасно.

Инвариантность дженериков

В отличие от массивов, дженерики инвариантны: List<String> не является подтипом List<Object>, и попытка подобного приведения типов приведёт к ошибке компиляции.

Когда следует избегать шаблонов?

Запись в коллекцию

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

Java
Скопировать код
List<Integer> list = new ArrayList<>();
list.add(10); // Понятно и безопасно.

Методы с конкретными типами

Для методов лучше использовать конкретные обобщённые типы для сохранения типовой безопасности.

Java
Скопировать код
public <T> void processItems(List<T> items) {
    // Обработка элементов указанного типа T.
}

Хранилище и Держатель: Каждому своё место

Хранилище для различных объектов

Collection<Object> предназначена для хранения объектов любых типов, однако вы не сможете присвоить ей коллекцию конкретного типа, например, Collection<String>.

Java
Скопировать код
Collection<Object> objects;
Collection<String> strings = new ArrayList<>();
// objects = strings; // Здесь возникнет ошибка компиляции.

Держатель любых коллекций

В отличие от Collection<Object>, в Collection<?> можно хранить коллекцию любого типа. Это демонстрирует применение полиморфизма.

Java
Скопировать код
Collection<?> holder;
Collection<String> strings = new ArrayList<>();
holder = strings; // Подходит для коллекций любого типа.

Расширяя возможности с ?

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

Java
Скопировать код
Map<?, ?> myMap = ... ; // Подходит для пар "ключ-значение" любых типов.

Визуализация

Представьте себе дженерики в Java как набор инструментов, среди которых есть инструменты с конкретными обозначениями и универсальные, помеченные знаком вопроса.

Markdown
Скопировать код
| Тип инструмента | Описание в дженериках     |
| --------------- | ------------------------- |
| С меткой        | Object                    |
| Универсальный   | ? (подстановочный символ) |

Object в дженериках — это инструмент с конкретным назначением, в то время как ? подходит для самых разнообразных задач.

Практическое применение ? и Object

Проектирование API для коллекций

Понимание различий между ? и Object важно для создания гибкого и адаптивного API.

Java
Скопировать код
public void printCollection(Collection<?> c) {
    for (Object item : c) {
        System.out.println(item); // Просто печатаем объекты.
    }
}

Использование в Stream API

При работе со стримами в Java использование подстановочных символов позволяет обрабатывать элементы независимо от их конкретных типов.

Java
Скопировать код
Stream<?> mixedStream = Stream.of(1, "two", 3.0);
mixedStream.map(Object::toString).forEach(System.out::println); // Обработка и вывод каждого элемента.

Полезные материалы