// чем отличаются list1, list2, list3 ?
// что можно положить в list1, list2, list3 ?
// что можно достать из list1, list2, list3 ?
В list3 можно присвоить(через =) только list<B>(инвариант)
В list2 можно присвоить(через =) только list<B> и list предков B, т.е. List<A>, List<Object>(ковариант)
В list1 можно присвоить(через =) только list<B> и list наследников B, т.е. List<C>(контрвариант)
Дальше сложнее,
в list1 через add ничего нельза добавить(Почему?)
В list2 через add можно добавить B и наследники B, т.е. С. Я так понимаю здесь работает capture conversion? Т.е. List<? super B> конвертируется в List<Object> или в List<B>,
поэтому можно добавлять B и его наследников, так как эти объектами будут как Object так и B?
В list3 через add можно добавить B и наследники B, т.е. С. Т.е. здесь тоже самое что и с List<? super B>? Или есть разница, что и когда выбирать?
в list1 через get можно читать B и A(Почему не C?)
В list2 через get можно читать Object?(Почему только Object)
В list3 через get можно читать B и A(В чем разница с List<? extends B>?)
Можно какие-то практические примеры когда какие типы более appropriate/fit?
Здравствуйте, Aleksei_Lekomtsev, Вы писали:
B>? Или есть разница, что и когда выбирать?
Это называется PECS — "Producer Extends, Consumer Super":
Producer Extends — Если вам нужен List для получения значений типа T (вы хотите читать значения T из списка), вы должны объявить его с использованием <? extends T>, например, List<? extends Integer>. Однако вы не можете добавлять элементы в этот список. Consumer Super — Если вам нужен List для записи значений типа T (вы хотите записывать значения T в список), вы должны объявить его с использованием <? super T>, например, List<? super Integer>. Однако нет гарантий относительно типа объекта, который вы можете прочитать из этого списка.
Если вам нужно как читать из списка, так и записывать в него, вы должны объявить его без использования подстановочных символов, например, List<Integer>.
? extends B это те типы, которые унаследованы от B, т.е. B или C или ещё что-то подобное. Какой именно тип — не указано.
Поэтому вопрос — почему add нельзя вызывать — он вроде очевиден. Мы не знаем, какой там тип. Может быть это List<B>. Может быть это List<C>. Может быть это ещё какой-то. Единственное, что мы знаем, что это не List<A> или List<Integer>. Ну и как тут что-то туда добавлять? Никак.
Что возвращает get() ? Ну объект какого-то класса, который унаследован от B. Какого — мы не знаем, но мы знаем, что мы можем прикастовать этот объект к B, он же от него унаследован. Поэтому с get() проблем нет.
Теперь возьмём List<? super B> list2. ? super B это какой-то из родительских классов. Может A, может Object. Поэтому add() работает без проблем — если наш объект хотя бы A, то никаких проблем нет. А вот с get — уже проблемки. Единственное, что точно знаем — что объекты в этом списке это наследники Object. Поэтому вызывать мы его можем, но типизации не дождёмся.