PECS и WildCards на пальцах
Принцип PECS (Producer Extends Consumer Super) использует Wildcard — метод описания поискового запроса с использованием метасимволов (символов-джокеров).
Данная статья не научный труд. Это простое объяснение зачем введены понятия PECS и WildCards, что бы понять и запомнить.
Для понимания WildCards необходимо четко представлять, что:
библиотечные контейнеры (коллекции) инвариантны — указателю, параметризированному классом Т, можно передать только контейнер параметризированный тем же классом Т;
библиотечные контейнеры хранят только ссылки (ни примитивы, ни строки, ни объекты они не хранят);
согласно принципам наследования в указатель типа Т можно передать экземпляр класса Т либо его наследников;
контейнер, параметризированный классом Т, хранит ссылки типа Т, которые ссылаются на адрес в памяти, в котором содержится либо экземпляр класса Т, либо экземпляр его наследника;
принцип PECS и WildCards созданы для лаконичности java-кодинга;
ограничения для WildCards имеют целью не допустить нарушения принципов наследования, а именно не допустить передачу экземпляра предка в указатель наследника.
Принцип применения WildCards для контейнеров в следующем:
в Consumer мы можем передать контейнеры предков класса (условно) T3 и самого класса T3
в Suplier мы можем передать контейнеры наследников класса (условно) T3 и самого класса T3
в Consumer мы будем иметь контейнеры с указателями на класс Т3 и его предков
в Suplier мы будем иметь контейнеры с указателями на класс Т3 и его потомков
в Consumer мы будем иметь указатели на Т3 и его предков, значит можем передать экземпляры Т3 и его потомков не нарушая принципов наследования.
в Consumer могут оказаться указатели от Т3 до Object, а указатели типа Object могут ссылаться на любые экземпляры иерархии Т. Как это получается:
указатели могут ссылаться на экземпляры Т3 и его потомков:
указатели могут ссылаться на экземпляры предков Т3, вплоть до Object
Следовательно, из Consumer без нарушения принципов наследования гарантированно можно получить экземпляр иерархии Т только в указатель типа Object. Затем эти экземпляры можно привести в соответствие к их классу явным приведением типов и передать в соответствующий указатель.
в Suplier мы будем иметь указатели на Т3 и его потомков. Теперь допустим, что на момент написания кода у Т3 имеется 4 наследника (Т4, Т5, Т6, Т7). Значит в Suplier может оказаться контейнер параметризованный от Т3 до Т7, содержащий указатели на экземпляры классов от Т3 до Т7. В такие указатели мы можем передавать экземпляры младшего класса в наследовании, т.е. Т7. Но мы не можем. Просто потому, что мы не можем ограничить наследование этих классов. Если разрешить передавать в Suplier экземпляры младшего на текущий момент класса с иерархии наследования, то со временем может возникнуть наследник Т8, которому ничто не запрещает быть переданным в Suplier, и который не сможет получить экземпляры класса Т7 в свои указатели, что нарушит принципы наследования. Поэтому в контейнер Suplier нельзя передавать элементы.
в Suplier могут оказаться указатели Т3 и ниже по иерархии, а указатели могут ссылаться так же на экземпляры Т и ниже по иерархии. Как это получается:
если в Suplier был передан контейнер параметризованный и содержащий указатели типа Т3 или его потомков, которые ссылаются на экземпляры Т3 или его потомков.
Следовательно, из Suplier без нарушения принципов наследования гарантированно можно получить экземпляр Т3 или ниже в иерархии в указатель Т3 или выше в иерархии. Затем эти экземпляры можно привести в соответствие к их классу явным приведением типов и передать в соответствующий указатель.
Из Consumer и из Suplier можно получить только содержащиеся в этих контейнерах элементы, получить в какой либо указатель содержащиеся в них контейнеры нельзя.