tgoop.com/dev_easy_notes/226
Last Update:
Идем дальше, есть три понятия инвариантность, ковариантность и контравариантность. Не пугайтесь названий, сейчас все раскидаем, что поймет каждый. Буду объяснять на примере List и двух классов Developer
и MobileDeveloper
. Как вы понимаете MobileDeveloper
наследует Developer
, т.е Developer
стоит выше по иерархии наследования. Это значит что мы любую ссылку на MobileDeveloper
можем привести к Developer
. Типо такого:MobileDeveloper mobileDeveloper = MobileDeveloper()
Developer developer = mobileDeveloper
Прежде чем пойдем дальше стоит понять такую штуку, как производный тип. Производный тип получается когда один класс, является одним из компонентов другого типа. Проще на примере, для наших классов это может быть – List<Developer>. List<Developer> – отдельный тип, однако в него входит наш Developer. Думаю тут суть ясна.
Значит, инвариантность это когда List<Developer> и List<MobileDeveloper> это два абсолютно разных типа. Другими словами мы не можем один тип привести к другому. Уже нельзя взять и привести ссылку на список List<MobileDeveloper>, к List<Developer> вас не пропустит компилятор. Все дженерики в Java и в Kotlin по дефолту инвариантные.
Ковариантность это сохранение иерархии в производных типах. Или проще, ковариантность это когда List<MobileDeveloper> является наследником List<Developer>. Раз он наследник, значит можно приводить одну ссылку в другой. Примерно так: List<MobileDeveloper> mobileDevelopers = new ArrayList<>();
List<? extends Developer> developers = mobileDevelopers;
Как вы заметили для этого нужно было использовать ? extends Developer. Это и есть синтаксическая реализация ковариантности в Java. Работает это примерно так, когда мы используем строку ? extends Developer мы говорим комплятору, в текущем списке, лежат объекты, которые 100% можно привести к Developer, ведь они или и есть Developer или ниже по иерархии наследованния.
Для чего это нужно? Очень удобно теперь делать функцию, которая итерируется по списку List<? extends Developer>. Ведь теперь не имеет значение какой именно наследник внутри этого списка. Помимо этого, мы теперь не можем в списке использовать модифицирующие операторы. А вот почему не можем, расскажу в следующем посте.
И последний компонент это Контравариантность. Он противоположен ковариантности. Другими словами контравариантность это когда List<Developer> является наследником List<MobileDeveloper>. Иерархия в производных типах поворачивается на 180 градусов. А пример вот такой:List<Developer> developers = new ArrayList<>();
List<? super MobileDeveloper> mobileDevelopers = developers;
Синтаксически контравариантность реализуется при помощи ? super MobileDeveloper. Этим мы говорим компилятору, что в списке mobileDevelopers либо MobileDeveloper, либо выше по иерархии.
Нужно это для того, чтобы делать функции заполнения. Аля такой, вот у меня метод fill(), он принимает вот такой список List<? super MobileDeveloper>. Теперь я могу передавать в этот метод как List<Developer> так и List<MobileDeveloper>, и он отработает одинаково хорошо. Ровно как и с Ковариантностью, у нас минус одна операция. Когда используем ? super MobileDeveloper нельзя использовать операции чтения. Если попробовать использовать get, компилятор упадет.
Откуда такие ограничения на чтение и запись при контравариантности и ковариантности расскажу в следующем посте. Это одна из самых сложных вещей в дженериках, поймете это, и больше у вас никогда не возникнет проблем.
BY Dev Easy Notes
Share with your friend now:
tgoop.com/dev_easy_notes/226