Как привести список наследуемых объектов к коллекции объектов в Java?
У меня есть тип коллекции:
Collection<A> collecA
И у меня есть список в моем объекте:
List<B> listB
Где B расширяет A
class B extends A { ... }
Но я не могу сделать следующее:
collecA = listB
Я не могу понять, почему, так как коллекция реализована в List.
4 ответа
Давайте на минутку предположим, что вы можете сделать то, что описали:
class B extends A { ... }
Collection<A> collecA;
List<B> listB;
collecA = listB; // normally an error, but lets pretend its allowed
collecA.add(new A()); // PROBLEM!
Вызов метода collecA.add(new A())
появляется нормально, так как collecA
это коллекция, которая содержит A
s. Однако, если вышеупомянутое назначение было разрешено, то у нас есть проблема, потому что collecA
действительно ссылка на List<B>
экземпляр - я только что добавил A
в список, который может содержать только B
s!
Аскер также сказал:
Я не могу понять, почему, так как коллекция реализована в List.
Неважно, что Collection является суперклассом List. Это назначение является незаконным, даже если вы использовали два списка.
class B extends A { ... }
List<A> listA;
List<B> listB;
listA = listB; // still an error, still leads to the same problem
Ключ в том, что List<A>
переменная может ссылаться только List
ы, которые могут держать A
s. Тем не менее, List<B>
экземпляр не может содержать A
s. Следовательно, List<A>
переменная, как listA
не может быть назначена ссылка на List<B>
экземпляр, на который ссылается listB
,
Или в более общем плане: B
будучи подклассом A
не означает, что SomeGenericClass<B>
это подкласс SomeGenericClass<A>
( JLS §4.10: Подтипирование не распространяется на универсальные типы: T <: U
не означает, что C<T> <: C<U>
,)
Именно этот пример / аналогия из Учебного руководства по Java Generics помогло мне понять это:
http://java.sun.com/docs/books/tutorial/java/generics/subtyping.html
"Понимание того, почему становится намного проще, если вы думаете о материальных объектах - вещах, которые вы на самом деле можете изобразить - таких как клетка:
// A cage is a collection of things, with bars to keep them in.
interface Cage<E> extends Collection<E>;
...
Cage<Lion> lionCage = ...;
Cage<Butterfly> butterflyCage = ...;
Но как насчет "клетки для животных"? Английский язык неоднозначен, поэтому, если быть точным, давайте предположим, что мы говорим о "клетке для животных":
Cage<Animal> animalCage = ...;
Это клетка, предназначенная для содержания всех видов животных, смешанных вместе. Он должен иметь достаточно прочные решетки, чтобы удерживать их во львах, и располагаться достаточно близко, чтобы удерживать бабочек.
...
Поскольку лев является своего рода животным (лев является подтипом животного), возникает вопрос: "Является ли клетка для льва своего рода клеткой для животных? Cage<Lion>
подтип Cage<Animal>
? ". Согласно приведенному выше определению клетки для животных ответ должен быть" нет ". Это удивительно! Но это имеет смысл, когда вы думаете об этом: нельзя предположить, что клетка для льва содержит бабочек, а клетка для бабочек не может Предполагается, что он держится на львах, поэтому ни одна клетка не может считаться клеткой для животных:
animalCage = lionCage; // compile-time error
animalCage = butterflyCage; // compile-time error
"
Collection<? extends A> collecA
Это исправляет это. Проблема не в List extends Collection
, но общие типы вместо.
Обобщения Java не являются ковариантными.
См. Теория и практика Java: Generics Gotchas для получения более подробной информации.
На странице показан простой пример, который разрушил бы систему типов, если бы она была ковариантной:
Представьте, что вы можете назначить List
List . Затем следующий код позволит вам поместить что-то, что не является целым числом, в список :
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415)); // ERROR: Adds a float to li, which is a list of Integers!
Вы можете назначить Список для Коллекции, но не Список для Коллекции.
Представьте, что случилось бы, если бы это было возможно:
List<B> = new ArrayList<B>();
Collection<A> collecA = listB; //Assume that this line compiles
collecA.add(new A());
B item = listB.get(0); //ClassCastException!
Как видите, мы "обманули" систему родовых типов, добавив экземпляр конкретного типа A в коллекцию, в которой должны были быть только объекты типа B (или потомки). Как следствие, последняя строка, которая выполняет неявное приведение к B, завершается с ошибкой ClassCastException. Что с этим не так? Компилятор не может гарантировать безопасность типов, и это противоречит одному из общих принципов Java.
Поэтому было решено, что список является коллекцией, но НЕ списком (или коллекцией).
В качестве дополнительного комментария интересно отметить, что массивы не следуют тем же правилам: String[] - это Object[], и назначения допустимы.