Страницы

Поиск по вопросам

вторник, 26 ноября 2019 г.

Использование wildcard в Generics Java


Здравствуйте.
Вот решил немного поглубже изучить Generic, так как возникла потребность написать свой класс. Раньше как-то не обращал внимание, а сейчас хочу разобраться.
Проблема в том, что не могу понять суть использования . Чита
много литературы, но все-таки не понял.
Есть еще одна противоположная wildcard - . С этим все очевидно
Мощная концепция. У нас есть суперкласс, в моем случае SomeClass. Может быть и интерфейс
Но тут неважно. У него есть свои методы. Которые обязательно будут! Не важно, сколько раз был унаследован и какие методы добавил. Нам важно, что мы уверены в том, что у любого наследника класса будут методы суперкласса, и нам все равно, как называется класс-наследник, просто вызываем методы, и все. Тут все ясно. 
НО вот  что-то совсем не понятно, какие преимущества предоставляет
Я понимаю, что это противоположная вещь , то есть просто один wildcard ограничивает сверху, другая снизу. Непонятно, зачем это использовать. 
Читал про концепцию PECS. Примеры смотрел, но так и не понял. 
Какой нам толк от того, что переданный класс будет суперклассом SomeClass. Ну, допустим
Object->ParentSomeClass->SomeClass будет такая иерархия. Ну, все классы валидно передадутся
и не будет compiler error. НО какой от этого толк? Я могу реализовать любые методы и в ParentSomeClass, и в SomeClass. Допустим, в ParentSomeClass не будет методов, которые есть в SomeClass. А в Object, естественно, не будет методов, которые реализовал ParentSomeClass. И какой тогда толк, одни проблемы.
Читал, что это используется, когда нужно добавлять элементы, в коллекцию, например. Но все-таки не понял сути.
Пожалуйста, объясните, как можно подробнее.     


Ответы

Ответ 1



Чтобы понять мнемонику PECS (provider - extends, consumer - super), рассмотрим качестве конкретного примера статический метод из стандартного класса Collections: public static void copy(List dest, List src) Этот метод копирует список типа Т в другой список. Он использует сразу оба вида ограничений: сверху для целевого списка и снизу для источника. Начнем с источника. Источником могут быть любые дочерние от T типы. Это логично нас не интересует, что именно мы перекладываем, нужно, чтобы оно хотя бы сводилось к T. src является для нас поставщиком (producer) объектов, и его тип мы позволяем расширять (extends). Это первые 2 буквы из мнемоники PECS. Теперь перейдем к целевому списку. В дальнейшем этим списком кто-то будет пользоватьс и, возможно, как-то его обрабатывать, и это накладывает определенные ограничения. Проще посмотреть это на примере. Предположим, что у нас есть класс Питомцев (являющийся подклассом Животных) и дочерние классы Собачонок и Котиков: class Animal { void feed() {} } class Pet extends Animal { void call() {} } class Kitty extends Pet{ void mew() {} } class Doge extends Pet{ void bark() {} } Теперь мы хотим скопировать из списка Питомцев в список Питомцев и позвать их. List src = ...; List dest = new ArrayList(); Collections.copy(dest, src); for(Pet p: dest) p.call(); Пока все ок. А что если бы в src были явно Котики (унаследованные от Питомцев)? List src = ...; List dest = new ArrayList(); Collections.copy(dest, src); for(Pet p: dest) p.call(); Без проблем, Котики откликаются на зов. При копировании в dest мы "потеряем" знани о том, что это были именно Котики, но по крайней мере они остаются Питомцами и их все еще можно позвать. Достаточно очевидно, что мы не можем скопировать Котиков в коллекцию Собачонок заставить их всех лаять: List src = ...; List dest = new ArrayList(); Collections.copy(dest, src); for(Doge doge: dest) doge.bark(); Очевидный нонсенс, Котиков нельзя просто грубо назвать Собачонками, система типо не позволит так сделать, поэтому мы не смогли бы расширить тип первого параметра в сигнатуре метода copy: public static void copy(List dest, List src); // так нельзя копировать! Но что же насчет super? Пусть наш код продолжит копировать Питомцев, а где-то в другом место другой код кормит любых Животных, и не только домашних. List dest = new ArrayList(); ..... List src = ...; Collections.copy(dest, src); .... for(Animal a: dest) a.feed(); Все Животные накормлены. Заметим, что целевой список является коллекцией Животных являющихся родительским классом для Питомцев. Общая концепция такова, что код, которы использует в дальнейшем список dest, не может в своих предположениях о его элемента опускаться ниже типа T, но может сколь угодно абстрагироваться к родительским типам. Список dest является потребителем (consumer), т.к. мы наполняем его и знаем, только, что его элементы совместимы с T, а значит сами являются T или лежат выше по иерархии (super). Это последние две буквы в мнемонике PECS. Надеюсь, что-то прояснил для вас.

Комментариев нет:

Отправить комментарий