Почему List <String> неприемлем как List <Object>?
Рассмотрим ниже метод doSomething(List<Object>)
, который принимает List<Object>
как параметр.
private void doSomething(List<Object> list) {
// do something
}
Теперь рассмотрим ниже фрагмент кода, который пытается вызвать doSomething()
, где я пытаюсь передать List<String>
в doSomething()
List<Object> objectList;
List<String> stringList;
doSomething(stringList); // compilation error incompatible types
doSomething(objectList); // works fine
Даже ниже код генерирует ошибку компиляции
objectList = stringList; // compilation error incompatible types
Мой вопрос: почему List<String>
не может быть передан методу, который принимает List<Object>
?
Ответы
Ответ 1
Этот общий вопрос в Java может показаться запутанным для любого, кто не очень хорошо знаком с Generics, так как на первый взгляд выглядит, что String - это объект, поэтому List<String>
можно использовать там, где требуется List<Object>
, но это неверно. Это приведет к ошибке компиляции.
Это имеет смысл, если вы сделаете еще один шаг, потому что List<Object>
может хранить что-либо, включая String
, Integer
и т.д., но List<String>
может хранить только Strings
.
Также посмотрите: Почему бы не наследовать из списка <T> ?
Ответ 2
Поскольку while String
extends Object
, List<String>
не распространяется List<Object>
Update:
В общем случае, если Foo
является подтипом (подкласс или подинтерфейс) Bar
, а G
- это объявление общего типа, это не тот случай, когда G<Foo>
является подтипом G<Bar>
.
Это потому, что коллекции меняются. В вашем случае If List<String>
был подтипом List<Object>
, тогда к нему могут быть добавлены типы, отличные от String
, когда список ссылается с использованием своего супертипа следующим образом:
List<String> stringList = new ArrayList<String>;
List<Object> objectList = stringList;// this does compile only if List<String> where subtypes of List<Object>
objectList.add(new Object());
String s = stringList.get(0);// attempt to assign an Object to a String :O
и компилятор Java должен предотвратить эти случаи.
Подробнее о этом в разделе "Учебное пособие по Java".
Ответ 3
Вы можете поместить объект неправильного типа в список, если это сработало:
private void doSomething(List<Object> list) {
list.add(new Integer(123)); // Should be fine, it an object
}
List<String> stringList = new ArrayList<String>();
doSomething(stringList); // If this worked....
String s = stringList.get(0); // ... you'd receive a ClassCastException here
Ответ 4
Причина этих ограничений связана с соображениями дисперсии.
Возьмите следующий код:
public void doSomething(List<Object> objects)
{
objects.add(new Object());
}
Развернув пример, вы можете попробовать сделать следующее:
List<String> strings = new ArrayList<String>();
string.add("S1");
doSomething(strings);
for (String s : strings)
{
System.out.println(s.length);
}
Надеюсь, это ясно, почему это сломается, если компилятор разрешил компиляцию этого кода (чего нет) - a ClassCastException
возникнет для второго элемента в списке при попытке применить Object
к String
.
Чтобы иметь возможность передавать обобщенные типы коллекций, вам необходимо сделать это:
public void doSomething(List<?> objects)
{
for (Object obj : objects)
{
System.out.println(obj.toString);
}
}
Опять же, компилятор наблюдает за вашей спиной, и если вы заменили System.out
на objects.add(new Object())
, компилятор не допустил бы этого, потому что objects
мог быть создан как List<String>
.
Дополнительные сведения о вариации см. в Wikipedia art Ковариация и контравариантность
Ответ 5
Иногда ожидается, что a List<Object>
будет супертипом a List<String>
, потому что Object
является супертипом String
.
Это ожидание связано с тем, что такое отношение типов существует для массивов:
Object[]
является супертипом String[]
, потому что Object
является супертипом String
. (Отношение этого типа известно как ковариация.)
Супер-подтип-отношение типов компонентов распространяется на соответствующие типы массивов.
Нет такого отношения типа для экземпляров родовых типов. (Параметрированные типы не ковариантны.)
Подробнее о здесь для более подробной информации
Ответ 6
Из учебных пособий по Java:
Позвольте проверить свое понимание дженериков. Является ли следующий фрагмент кода законным?
List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2
Линия 1 безусловно легальна. Более сложная часть вопроса - строка 2. Это сводится к вопросу: является ли список строк списком объектов. Большинство людей инстинктивно отвечают: "Конечно!"
Хорошо, взгляните на следующие несколько строк:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: Attempts to assign an Object to a String!
Здесь мы получили псевдонимы ls и lo. Получив доступ к ls, список String, через псевдоним lo, мы можем вставить в него произвольные объекты. В результате ls больше не держит Strings, и когда мы пытаемся извлечь что-то из этого, мы получаем грубый сюрприз.
Компилятор Java предотвратит это, конечно. Строка 2 вызовет ошибку времени компиляции.
Источник: Общие и подтипирование
Ответ 7
Если вы не уверены, какой тип данных он примет, вы можете использовать Generics на Java, как показано ниже.
public static void doSomething(List<?> data) {
}
public static void main(String [] args) {
List<Object> objectList = new ArrayList<Object>();
List<String> stringList = new ArrayList<String>();
doSomething(objectList);
doSomething(stringList);
}
Но при использовании данных вам будет необходимо указать правильный тип данных как тип Cast