
Содержание статьи
от Фабиана Терха

Типы
Java — это статически типизированный язык, означающий, что вы должны сначала объявить переменную и ее тип перед ее использованием.
Например: int myInteger = 42;
Введите общие типы.
Универсальные типы
Определение: «А родовой тип это общий класс или интерфейс, который параметризирован над типами».
По сути, общие типы позволяют написать общий общий класс (или метод), работающий с разными типами, позволяющий повторно использовать код.
Вместо уточнения obj
быть с int
тип, или a String
тип или любой другой тип, который вы определяете Box
class, чтобы принять параметр типа <
;T>. Тогда вы блn
используйте T, чтобы представить этот общий тип в любой части вашего класса.
Теперь введите ковариацию и контравариацию.
Ковариантность и контравариантность
Определение
Дисперсия относится к тому, как подтипирование между более сложными типами связано с подтипированием между их компонентами (источник).
Легкое для запоминания (и чрезвычайно неформальное) определение ковариации и контравариации:
- Ковариация: принять подтипы
- Контравариантность: принимать супертипы
Массивы
на Java, массивы ковариантныечто имеет 2 последствия.
Во-первых, массив типов T[]
может содержать элементы типа T
и их подтипы.
Number[] nums = new Number[5];nums[0] = new Integer(1); // Oknums[1] = new Double(2.0); // Ok
Во-вторых, массив типов S[]
является подтипом T[]
если S
является подтипом T
.
Integer[] intArr = new Integer[5];Number[] numArr = intArr; // Ok
Однако важно помнить, что: (1) numArr
является ссылкой типа ссылки Number[]
к «настоящему объекту» intArr
«фактического типа» Integer[]
.
Таким образом, следующая строка будет скомпилирована отлично, но создаст среду выполнения ArrayStoreException
(из-за загрязнения кучи):
numArr[0] = 1.23; // Not ok
Он создает исключение при выполнении, поскольку Java знает при выполнении, что «фактический объект» intArr
на самом деле является массивом Integer
.
генерики
С общими типами Java нет возможности узнать при выполнении информации о типе параметров типа из-за стирания типа. Поэтому он не может защитить от загрязнения кучи во время выполнения.
Таким образом, генерики являются инвариантными.
ArrayList<Integer> intArrList = new ArrayList<>();ArrayList<Number> numArrList = intArrList; // Not okArrayList<Integer> anotherIntArrList = intArrList; // Ok
Параметры типа должны точно совпадать, чтобы защитить от загрязнения кучи.
Но введите символы подстановки.
Подстановочные знаки, ковариация и контравариация
С подстановочными знаками генерики могут поддерживать ковариацию и контравариантность.
Настроив предыдущий пример, мы получаем работающее!
ArrayList<Integer> intArrList = new ArrayList<>();ArrayList<? super Integer> numArrList = intArrList; // Ok
Вопросительный знак «?» ссылается на символ подстановки, представляющий неизвестный тип. Он может быть ограничен снизу, что ограничивает неизвестный тип определенным типом или его супертипом.
Следовательно, в строке 2, ? super Integer
переводится как «любой тип, являющийся типом Integer или его супертипом».
Вы также можете ограничить верхний знак подстановки, ограничивающий неизвестный тип определенным типом или его подтипом, используя ? extends Integer
.
Только чтение и только запись
Ковариация и контравариантность приносят некоторые интересные результаты. Ковариантные типы доступны только для чтения, а контравариантные – только для записи.
Помните, что типы ковариантов принимают подтипы, следовательно ArrayList<? extends Numb
er> может содержать любой объект of a
Тип числа или его подтип.
В этом примере строка 9 работает, потому что мы можем быть уверены, что все, что мы получаем из ArrayList, может быть передано в Number
тип (потому что если он расширяется Number
по определению, это это Number
).
Но nums.add()
не работает, потому что мы не можем быть уверены в «фактическом типе» объекта. Все, что мы знаем, это то, что это должно быть a Number
или его подтипы (например, Integer, Double, Long и т.п.).
С контравариантностью верно наоборот.
Строка 9 работает, потому что мы можем быть уверены, что каким бы «фактическим типом» объекта ни был, он должен быть Integer
или его супертип, и таким образом принять an Integer
объект.
Но строчка 10 не работает, потому что мы не можем быть уверены, что получим Integer
. Например, nums
может ссылаться на ArrayList Objects
.
Приложения
Поэтому, поскольку ковариантные типы доступны только для чтения, а контравариантные – только для записи (свободно говоря), мы можем вывести следующее правило: «Производитель расширяется, потребитель супер».
Объект, похожий на производителя, создающий объекты типа T
может иметь параметр типа <? extends
T>, в то время как потребительский объект, потребляющий объекты
тип T может быть типа parameter <?
супер T>.