Типы
Java - это язык со статической типизацией, что означает, что вы должны сначала объявить переменную и ее тип, прежде чем использовать ее.
Например: int myInteger = 42;
Введите общие типы.
Универсальные типы
Определение: Универсальный тип - это универсальный класс или интерфейс, параметризованный по типам.
По сути, универсальные типы позволяют вам написать общий универсальный класс (или метод), который работает с разными типами, что позволяет повторно использовать код.
Вместо того, чтобы указывать obj
как тип int
, тип String
или любой другой тип, вы определяете класс Box
для принятия параметра типа <T>
. Затем вы можете использовать T
для представления этого универсального типа в любой части вашего класса.
Теперь введите ковариацию и контравариантность.
Ковариация и контравариантность
Определение
Дисперсия относится к тому, как выделение подтипов между более сложными типами связано с подтипами между их компонентами (источником).
Легко запоминающееся (и крайне неформальное) определение ковариации и контравариантности:
- Ковариация: принять подтипы
- Контравариантность: принимать супертипы
Массивы
В Java массивы ковариантны, что имеет два следствия.
Во-первых, массив типа T[]
может содержать элементы типа T
и его подтипы.
Number[] nums = new Number[5]; nums[0] = new Integer(1); // Ok nums[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 ok ArrayList<Integer> anotherIntArrList = intArrList; // Ok
Параметры типа должны точно совпадать для защиты от загрязнения кучи.
Но введите подстановочные знаки.
Подстановочные знаки, ковариация и контравариантность
С помощью подстановочных знаков универсальные шаблоны могут поддерживать ковариацию и контравариантность.
Изменяя предыдущий пример, мы получаем вот что, и оно работает!
ArrayList<Integer> intArrList = new ArrayList<>(); ArrayList<? super Integer> numArrList = intArrList; // Ok
Знак вопроса "?" относится к подстановочному знаку, представляющему неизвестный тип. Он может быть ограниченным снизу, что ограничивает неизвестный тип определенным типом или его супертипом.
Следовательно, в строке 2 ? super Integer
переводится как «любой тип, являющийся целочисленным типом или его супертипом».
Вы также можете ограничить верхний предел подстановочного знака, который ограничивает неизвестный тип определенным типом или его подтипом, используя ? extends Integer
.
Только для чтения и только для записи
Ковариация и контравариантность дают интересные результаты. Ковариантные типы доступны только для чтения, а контравариантные - только для записи.
Помните, что ковариантные типы принимают подтипы, поэтому ArrayList<? extends Number>
может содержать любой объект типа Number
или его подтипа.
В этом примере работает строка 9, потому что мы можем быть уверены, что все, что мы получаем из ArrayList, можно преобразовать в тип Number
(потому что, если он расширяет Number
, по определению, это Number
).
Но nums.add()
не работает, потому что мы не можем быть уверены в «фактическом типе» объекта. Все, что мы знаем, это то, что это должно быть Number
или его подтипы (например, Integer, Double, Long и т. Д.).
В случае контравариантности верно обратное.
Строка 9 работает, потому что мы можем быть уверены, что каким бы ни был «фактический тип» объекта, он должен быть Integer
или его супертипом и, таким образом, принимать объект Integer
.
Но строка 10 не работает, потому что мы не можем быть уверены, что получим Integer
. Например, nums
может ссылаться на список массивов Objects
.
Приложения
Следовательно, поскольку ковариантные типы доступны только для чтения, а контравариантные типы доступны только для записи (грубо говоря), мы можем вывести следующее практическое правило: «Производитель расширяет, потребитель супер».
Объект, подобный производителю, который создает объекты типа T
, может иметь параметр типа <? extends T>
, в то время как объект, подобный потребителю, который потребляет объекты типа T
, может иметь параметр типа <? super T>
.