Я постараюсь ответить на это подробно. До дженериков у нас был только List
(необработанный список), и он мог содержать практически все, что мы могли придумать.
List rawList = new ArrayList();
rawList.add("String Item");
rawList.add(new Car("VW"));
rawList.add(new Runnable() {
@Override
public void run() {
// do some work.
}
});
Основная проблема с необработанным списком состоит в том, что когда мы хотим извлечь какой-либо элемент из этого списка, он может гарантировать только то, что он будет Object
, и по этой причине нам нужно использовать приведение как:
Object item = rawList.get(0); // we get object without casting.
String sameItem = (String) rawList.get(0); // we can use casting which may fail at runtime.
Таким образом, можно сделать вывод, что List
может хранить объект (почти все это объекты в Java) и всегда возвращает объект.
Дженерики
Теперь давайте поговорим о дженериках. Рассмотрим следующий пример:
List<String> stringsList = new ArrayList<>();
stringsList.add("Apple");
stringsList.add("Ball");
stringsList.add(new Car("Fiat")); //error
String stringItem = stringsList.get(0);
В приведенном выше случае мы не можем вставить ничего, кроме String
в stringsList
, поскольку компилятор Java применяет строгую проверку типов к универсальному коду и выдает ошибки, если код нарушает безопасность типов. И мы получаем ошибку, когда пытаемся вставить в нее экземпляр Car
. Также он исключает приведение, поскольку вы можете проверить, когда мы invoke
получаем метод. Проверьте эту ссылку для понимания , почему мы должны использовать дженерики .
List<Object>
Если вы прочтете об удалении типа, то поймете, что List<String>, List<Long>, List<Animal>
и т. Д. Будут иметь разные статические типы во время компиляции, но будут иметь одинаковый динамический тип List
во время выполнения.
Если у нас есть List<Object>
, тогда он может хранить только Object
в нем, и почти все это Object
в Java. Таким образом, мы можем иметь:
List<Object> objectList = new ArrayList<Object>();
objectList.add("String Item");
objectList.add(new Car("VW"));
objectList.add(new Runnable() {
@Override
public void run() {
}
});
Object item = objectList.get(0); // we get object without casting as list contains Object
String sameItem = (String) objectList.get(0); // we can use casting which may fail at runtime.
Кажется, List<Object>
и List
одинаковы, но на самом деле это не так. Рассмотрим следующий случай:
List<String> tempStringList = new ArrayList<>();
rawList = tempStringList; // Ok as we can assign any list to raw list.
objectList = tempStringList; // error as List<String> is not subtype of List<Obejct> becuase generics are not convariant.
Вы можете видеть, что мы можем назначить любой список необработанному списку, и основной причиной этого является обеспечение обратной совместимости. Кроме того, List<String>
будет преобразовано в List
во время выполнения из-за стирания типа, и назначение все равно будет в порядке.
Но List<Object>
означает, что он может ссылаться только на список объектов, а также может хранить только объекты. Даже если String
является подтипом Object
, мы не можем присвоить List<String>
List<Object>
, поскольку генерики не являются ковариантными, как массивы. Они инвариантны. Также проверьте эту ссылку для получения дополнительной информации. Также проверьте разницу между List
и List<Object>
в этом вопросе .
List<?>
Теперь у нас осталось List<?>
, что в основном означает список неизвестного типа и может ссылаться на любой список.
List<?> crazyList = new ArrayList<String>();
List<String> stringsList = new ArrayList<>();
stringsList.add("Apple");
stringsList.add("Ball");
crazyList = stringsList; // fine
Символ ?
известен как подстановочный знак, а List<?>
- это список неограниченных подстановочных знаков. Есть определенные моменты, которые нужно соблюдать сейчас.
Мы не можем создать экземпляр этого списка, так как следующий код не скомпилируется:
List<?> crazyList = new ArrayList<?>(); // any list.
Можно сказать, что параметризованный тип с подстановочными знаками больше похож на тип интерфейса, поскольку мы можем использовать его для ссылки на объект совместимого типа, но не для себя.
List<?> crazyList2 = new ArrayList<String>();
Мы не можем вставить в него какой-либо элемент, так как мы не знаем, каким на самом деле будет тип.
crazyList2.add("Apple"); // error as you dont actually know what is that type.
Теперь возникает вопрос, когда бы я хотел использовать List<?>
?
Вы можете думать об этом как о доступном только для чтения списке, где вас не волнует тип элементов. Вы можете использовать его для вызова таких методов, как возврат длины списка, его печать и т. Д.
public static void print(List<?> list){
System.out.println(list);
}
Вы также можете проверить разницу между List, List<?>, List<T>, List<E>, and List<Object>
здесь .