Что такое ковариантность типов
Теория программирования: Вариантность
Здравствуйте, меня зовут Дмитрий Карловский и я… хочу поведать вам о фундаментальной особенности систем типов, которую зачастую или вообще не понимают или понимают не правильно через призму реализации конкретного языка, который ввиду эволюционного развития имеет много атавизмов. Поэтому, даже если вы думаете, что знаете, что такое «вариантность», постарайтесь взглянуть на проблематику свежим взглядом. Начнём мы с самых основ, так что даже новичок всё поймёт. А продолжим без воды, чтобы даже профи было полезно для структурирования своих знаний. Примеры кода будут на псевдоязыке похожем на TypeScript. Потом будут разобраны подходы уже нескольких реальных языков. А если же вы разрабатываете свой язык, то данная статья поможет вам не наступить на чужие грабли.
Аргументы и параметры
Параметр — это то, что мы принимаем. Описывая тип параметра мы задаём ограничение на множество типов, которые можно нам передать. Несколько примеров :
Аргумент — это то, что мы передаём. В момент передачи аргумент всегда имеет какой-то конкретный тип. Тем не менее, при статическом анализе конкретный тип может быть не известен из-за чего компилятор оперирует опять же ограничениями на тип. Несколько примеров:
Подтипы
Типы могут могут образовывать иерархию. Подтип — это частный случай надтипа. Подтип может образовываться путём сужения множества возможных значений надтипа. Например, тип Natural является подтипом Integer и Positive. И все трое одновременно являются подтипами Real. А тип Prime является подтипом всех вышеперечисленных. В то же время типы Positive и Integer являются пересекающимися, но ни один из них не является сужением другого.
Другой способ образовать подтип — расширить его, объединив с другим ортогональным ему типом. Например, есть «цветная фигура» имеющая свойство «цвет», а есть «квадрат» имеющий свойство «высота». Объединив эти типы мы получим «цветной квадрат». А добавив «круг» с его «радиусом» можем получить «цветной цилиндр».
Иерархии
Для дальнейшего повествования нам понадобится небольшая иерархия животных и аналогичная ей иерархия клеток.
Всё, что ниже на рисунке является сужением типа выше. Клетка с питомцем может содержать лишь домашних животных, но не диких. Клетка с собакой может содержать лишь собак.
Ковариантность
Самое простое и понятное — это ограничение надтипа или ковариантность. В следующем примере параметр функции ковариантен указанному для него типу. То есть функция может принимать как сам этот тип, так и любой его подтип, но не может принимать надтипы или иные типы.
Так как мы ничего не меняем в клетке, то спокойно можем передавать функции клетку с кошкой, так как она — не более, чем частный случай клетки с питомцем.
Контравариантность
Чуть сложнее понять ограничение подтипа или контравариантность. В следующем примере параметр функции контравариантен указанному для него типу. То есть функция может принимать как сам этот тип, так и любой его надтип, но не может принимать подтипы или иные типы.
Мы не можем передать клетку с кошкой, так как функция может засунуть туда собаку, что не допустимо. А вот клетку с любым животным можно смело передавать, так как и кошку и собаку вполне можно туда помещать.
Инвариантность
Ограничение подтипа и надтипа могут быть одновременно. Такой случай называется инвариантностью. В следующем примере параметр функции инвариантен указанному для него типу. То есть функция может принимать только указанный тип и никакой больше.
Бивариантность
Нельзя не упомянуть и экзотическое отсутствие ограничений — бивариантность. В следующем примере функция может принять любой тип являющийся надтипом или подтипом.
В неё можно передать клетку с животным. Тогда она проверит, что в клетке находится питомец, иначе положит внутрь случайного питомца. А можно передать и, например, клетку с кошкой, тогда она просто ничего не сделает.
Обобщения
Некоторые считают, что вариантность как-то связана с обобщениями. Зачастую потому, что про вариантность часто объясняют на примере обобщённых контейнеров. Однако, во всём повествовании у нас до сих пор не было ни единого обобщения — сплошь конкретные классы:
Сделано это было, чтобы показать, что проблемы вариантности никак с обобщениями не связаны. Обобщения нужны лишь, чтобы уменьшить копипасту. Например, код выше можно переписать через простое обобщение:
И теперь можно создавать экземпляры любых клеток:
Декларация ограничений
Обратите внимание, что сигнатуры у всех четырёх ранее приведённых функций совершенно одинаковые:
То есть такое описание принимаемых параметров функции не обладает полнотой — по нему нельзя сказать что в функцию можно передавать. Ну разве что однозначно видно, что клетку с лисой передавать в неё точно не стоит.
Поэтому в современных языках есть средства для явного указания какие у параметра ограничения на типы. Например, модификаторы in и out в C#:
К сожалению, в C# для каждого варианта модификаторов необходимо заводить по отдельному интерфейсу. Кроме того, насколько я понял, бивариантность в C# вообще невыразима.
Выходные параметры
Функции могут не только принимать, но и возвращать значения. В общем случае, возвращаемое значение может быть и не одно. В качестве примера возьмём функцию принимающую клетку с питомцем и возвращающую двух питомцев.
Такая функция эквивалентна функции, принимающей помимо одного входного параметра, ещё и два выходных.
Внешний код выделяет на стеке дополнительную память, чтобы функция положила в неё всё, что хочет вернуть. А по завершении, вызывающий код уже сможет воспользоваться этими контейнерами в своих целях.
Из эквивалентности этих двух функций следует, что возвращаемые функцией значения в отличие от параметров всегда контравариантны указанному выходному типу. Ибо функция может в них писать, но не может из них читать.
Методы объектов
Методы объектов — это такие функции, которые принимают дополнительный указатель на объект в качестве неявного параметра. То есть следующие две функции эквивалентны.
Однако, важно отметить, что метод, в отличие от обычной функции, является также и членом класса, что является расширением типа. Приводит это к тому, что появляется дополнительное ограничение надтипа, для функций, вызывающих этот метод:
Мы не можем передать в неё такой надтип, где метод pushPet ещё не определён. Это похоже на случай с инвариантностью тем, что есть ограничение и снизу и сверху. Однако, место определения метода pushPet может быть выше по иерархии. И именно там будет ограничение надтипа.
Принцип Подстановки Барбары Лисков (LSP)
Многие считают, что отношение надтип-подтип определяется не исходя из упомянутых ранее способов сужения и расширения типа, а возможностью подставить подтип в любое место использования надтипа. По всей видимости, причина этого заблуждения именно в LSP. Однако, давайте прочитаем определение этого принципа внимательно, обратив внимание, что первично, а что вторично:
Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом и не нарушая корректность работы программы.
Для неизменяемых (в том числе не ссылающихся на изменяемые) объектов этот принцип выполняется автоматически, так как неоткуда взяться ограничению подтипа.
С изменяемыми же всё сложнее, так как следующие две ситуации являются взаимоисключающими для принципа LSP:
Соответственно, остаётся лишь три пути:
TypeScript
В тайпскрипте логика простая: все параметры функции считаются ковариантными (что не верно), а возвращаемые значения — контравариантными (что верно). Ранее было показано, что параметры функции могут иметь совершенно любую вариантность в зависимости от того, что эта функция с этими параметрами делает. Поэтому получаются такие вот казусы:
Чтобы решить эту проблему приходится помогать компилятору довольно нетривиальным кодом:
FlowJS
FlowJS имеет более продвинутую систему типов. В частности, в описании типа можно указать его вариантность для обобщённых параметров и для полей объектов. На нашем примере с клетками выглядит это примерно так:
Бивариантность тут невыразима. К сожалению, мне не удалось найти способа более удобно задавать вариантность не описывая типы всех полей явно. Например, как-то так:
C Sharp
C# изначально проектировался без какого-либо понимания вариантности. Однако, впоследствии в нём были добавлены in и out модификаторы параметров, что позволило компилятору правильно проверять типы передаваемых аргументов. К сожалению, использовать эти модификаторы опять же не очень удобно.
К Java возможность переключения вариантности была добавлена довольно поздно и лишь для обобщённых параметров, которые сами-то появились сравнительно недавно. Если же параметр не обобщённый, то беда.
C++ благодаря мощной системе шаблонов может выразить различные вариантности, но кода, конечно получается много.
D не имеет вменяемых средств явного указания вариантности, зато он умеет сам выводить типы исходя из их использования.
Эпилог
На этом пока всё. Надеюсь изложенный материал помог вам лучше понять ограничения на типы, и как они реализованы в разных языках. Где-то лучше, где-то хуже, где-то никак, но в целом — так себе. Возможно именно вы разработаете язык, в котором всё это будет реализовано и удобно, и типобезопасно. А пока присоединяйтесь к нашему телеграм чату, где мы иногда обсуждаем теоретические концепции языков программирования.
Ковариантный тип возвращаемого значения в Java
Изучите, что такое ковариационные и ковариантные типы возвращаемых данных и как они ведут себя в Java.
1. Обзор
В этом уроке мы подробнее рассмотрим ковариантный тип возвращаемого значения в Java. Прежде чем рассматривать ковариацию с точки зрения возвращаемого типа, давайте посмотрим, что это значит.
2. Ковариация
Ковариацию можно рассматривать как контракт на то, как принимается подтип, когда определен только супертип.
Давайте рассмотрим несколько основных примеров ковариации:
3. Ковариантный Тип Возврата
В результате Object в качестве возвращаемого типа мы можем иметь более конкретный тип возвращаемого значения в дочернем классе. Это будет ковариантный тип возврата и будет производить числа из последовательностей символов:
4. Использование структуры
Например, давайте рассмотрим следующий сценарий производителя:
Тем не менее, мы все еще ссылаемся на результат через объект . Всякий раз, когда мы начинаем использовать явную ссылку на Целочисленного производителя, мы можем получить результат в виде Целого числа без понижения:
Хорошо известный сценарий-это Объект# клон метод, который возвращает Объект по умолчанию. Всякий раз, когда мы переопределяем клон() метод, объект ковариантных типов возврата позволяет нам иметь более конкретный объект возврата, чем метод, объект ковариантных типов возврата позволяет нам иметь более конкретный объект возврата, чем метод, объект ковариантных типов возврата позволяет нам иметь более конкретный объект возврата, чем
метод, объект ковариантных типов возврата позволяет нам иметь более конкретный объект возврата, чем
метод, объект ковариантных типов возврата позволяет нам иметь более конкретный объект возврата, чем
метод, объект ковариантных типов возврата позволяет нам иметь более конкретный объект возврата, чем
Вариантность в программировании
До сих пор не можете спать, пытаясь осмыслить понятия ковариантности и контравариантности? Чувствуете, как они дышат вам в спину, но когда оборачиваетесь ничего не находите? Есть решение!
Меня зовут Никита, и сегодня мы попытаемся заставить механизм в голове работать корректно. Вас ожидает максимально доступное рассмотрение темы вариантности в примерах. Добро пожаловать под кат.
Брифинг
Вариантность в данном посте разбирается безотносительно к какому-либо языку программирования. Примеры в разделе практики написаны на псевдоязыке (он чудом оказался похож на C#) и поэтому не обязаны компилироваться вашим любимым компилятором. Приступим.
Хитрости терминологии
В документации, технической литературе и других источниках вы могли встречаться с различными названиями для явлений вариантности. Больше не стоит пугаться и путаться.
Термины ковариантность и ковариация эквивалентны (по крайней мере в программировании). Более того, термины контравариантность и контравариация также эквивалентны. Так, например, термины ковариантность и контравариантность используется в Википедии и у Троелсена (в переводе). А термины ковариация и контравариация встречаются, например, на MSDN и у Скита (в переводе).
В английском языке всё проще — covariance и contravariance.
Теория
Вариантность — перенос наследования исходных типов на производные от них типы. Под производными типами понимаются контейнеры, делегаты, обобщения, а не типы, связанные отношениями «предок-потомок». Различными видами вариантности являются ковариантность, контравариантность и инвариантность.
Ковариантность — перенос наследования исходных типов на производные от них типы в прямом порядке.
Контравариантность — перенос наследования исходных типов на производные от них типы в обратном порядке.
Инвариантность — ситуация, когда наследование исходных типов не переносится на производные.
Если у производных типов наблюдается ковариантность, говорят, что они ковариантны исходному типу. Если у производных типов наблюдается контравариантность, говорят, что они контравариантны исходному типу. Если у производных типов не наблюдается ни того, ни другого, говорят, что они инвариантны.
Вот и всё, что нужно знать. Конечно, тем кто первый раз сталкивается с вариантностью, трудно вникнуть. Поэтому рассмотрим конкретные примеры.
Практика
Для чего всё это?
Вся суть вариантности состоит в использовании в производных типах преимуществ наследования. Известно, что если два типа связаны отношением «предок-потомок», то объект потомка может храниться в переменной типа предка. На практике это значит, что мы можем использовать для каких-либо операций объекты потомка вместо объектов предка. Тем самым, можно писать более гибкий и короткий код для выполнения действий поддерживаемых разными потомками с общим предком.
Исходная иерархия и производные типы
Для начала опишем иерархию типов, которой будем оперировать. Вверху иерархии у нас находится Device (устройство), потомками которого являются Mouse (мышь), Keyboard (клавиатура). У Mouse в свою очередь тоже есть потомки — WiredMouse (проводная мышь), WirelessMouse (беспроводная мышь).
Все любят контейнеры. На их примере наиболее просто объяснить, что подразумевается под производными типами. Если говорить о списках как производных типах, то для типа Device производным будет
List (список устройств). Аналогично, для типа Keyboard производным будет List (список клавиатур). Думаю, если и были сомнения, то теперь их нет.
Классическая ковариантность
Ковариантность также легче изучать на примере контейнеров. Для этого выделим часть иерархии (ветвь) — Keyboard : Device (клавиатура является устройством, клавиатура частный случай устройства). Опять возьмём списки и построим ковариантную производную ветвь — List : List (список клавиатур является частным случаем списка устройств). Как видим, наследование передалось в прямом порядке.
Рассмотрим пример кода. Есть функция, которая принимает список устройств List и совершает над ними какие-то манипуляции. Как вы уже догадались, в эту функцию можно передать список клавиатур List :
Классическая контравариантность
Каноническим для изучения контравариантности является рассмотрение её на основе делегатов. Допустим, у нас есть обобщённый делегат:
Немного инвариантности
Если производные типы инвариантны к исходным типам, то для ветви Keyboard : Device не образуется ни ковариантной ( List : List ), ни контравариантной ( Action : Action ) ветви. Это значит, что нет никакой связи между производными типами. Как видим, наследование не переносится.
А что если?
Неочевидная ковариантность
Неочевидная контравариантность
Сакральный смысл
Рассмотренные выше экзотические виды вариантности имеют, разве что, академическую ценность. Сложно придумать реальную задачу, которая легче решается при наличии такого рода возможностей. Стоит запомнить, что ковариантность и контравариантность могут вызывать ошибки времени выполнения. Для их устранения требуется вводить определённые ограничения. Компиляторы, как правило, такие ограничения не вводят.
Безопасность для контейнеров
Двойные стандарты для делегатов
Разумным для делегатов является ковариантность для выходного значения и контравариантность для входных параметров (исключая передачу по ссылке). В случае соблюдения данных условий ошибок времени выполнения не возникает.
Дебрифинг
Представленных примеров достаточно для понимания принципов работы вариантности. Данные о её поддержке разными типами вашего любимого языка ищите в соответствующей спецификации. Если что-то пошло не так — закройте глаза, выдохните и выпейте чай. После этого попытайтесь снова. Спасибо за внимание.
Возможно более правильным определением вариантности является предложенное Эриком Липпертом. Спасибо Alex_sik за ссылку на статью.
Совместимость присваивания, assignment compatibility — это возможность присвоить значение более частного типа совместимой переменной более общего типа.
Вариантность — это сохранение совместимости присваивания исходных типов у производных типов.
Ковариантность — это сохранение совместимости присваивания исходных типов у производных в прямом порядке.
Контравариантность — это сохранение совместимости присваивания исходных типов у производных в обратном порядке.
Ковариация и контравариантность (C#)
В C# ковариация и контрвариантность позволяют использовать неявное преобразование ссылок для типов массивов и делегатов, а также для аргументов универсального типа. Ковариация сохраняет совместимость присваивания, а при контрвариантности присваивание начинает работать противоположным образом.
Следующий код показывает разницу между совместимостью присваивания, ковариацией и контрвариантностью.
Ковариация для массивов позволяет неявно преобразовать массив более производного типа в массив менее производного типа. Но эта операция не является типобезопасной, как показано в следующем примере кода.
Поддержка ковариации и контрвариантности для групп методов позволяет сопоставить сигнатуры методов с типами делегатов. За счет этого вы можете назначать делегатам не только методы с совпадающими сигнатурами, но и методы, которые возвращают более производные типы (ковариация) или принимают параметры с менее производными типами (контрвариантность), чем задает тип делегата. Дополнительные сведения см. в разделах Вариативность в делегатах (C#) и Использование вариативности в делегатах (C#).
В следующем примере кода демонстрируется поддержка ковариации и контрвариантности для групп методов.
В следующем примере кода показано неявное преобразование ссылок для универсальных интерфейсов.
Универсальный интерфейс или делегат называется вариантным, если его универсальные параметры объявлены ковариантными или контрвариантными. C# позволяет вам создавать собственные вариантные интерфейсы и делегаты. Дополнительные сведения см. в разделах Создание вариантных универсальных интерфейсов (C#) и Вариативность в делегатах (C#).
Пришел, увидел, обобщил: погружаемся в Java Generics
Java Generics — это одно из самых значительных изменений за всю историю языка Java. «Дженерики», доступные с Java 5, сделали использование Java Collection Framework проще, удобнее и безопаснее. Ошибки, связанные с некорректным использованием типов, теперь обнаруживаются на этапе компиляции. Да и сам язык Java стал еще безопаснее. Несмотря на кажущуюся простоту обобщенных типов, многие разработчики сталкиваются с трудностями при их использовании. В этом посте я расскажу об особенностях работы с Java Generics, чтобы этих трудностей у вас было поменьше. Пригодится, если вы не гуру в дженериках, и поможет избежать много трудностей при погружении в тему.
Работа с коллекциями
Предположим, банку нужно подсчитать сумму сбережений на счетах клиентов. До появления «дженериков» метод вычисления суммы выглядел так:
С появлением Generics необходимость в проверке и приведении типа отпала:
Во второй строчке проверки необходимость тоже отпадала. Если потребуется, приведение типов ( casting ) будет сделано на этапе компиляции.
Принцип подстановки
Тип | Подтип |
Number | Integer |
List | ArrayList |
Collection | List |
Iterable | Collection |
Примеры отношения тип/подтип
Вот пример использования принципа подстановки в Java:
Ковариантность, контравариантность и инвариантность
Но если мы попытаемся изменить содержимое массива через переменную arr и запишем туда число 42, то получим ArrayStoreException на этапе выполнения программы, поскольку 42 является не строкой, а числом. В этом недостаток ковариантности массивов Java: мы не можем выполнить проверки на этапе компиляции, и что-то может сломаться уже в рантайме.
«Дженерики» инвариантны. Приведем пример:
Wildcards
Всегда ли Generics инварианты? Нет. Приведу примеры:
Это ковариантность. List — подтип List
extends B — символ подстановки с указанием верхней границы super B — символ подстановки с указанием нижней границы где B — представляет собой границу 2. Почему нельзя получить элемент из списка ниже? The Get and Put Principle или PECS (Producer Extends Consumer Super)Особенность wildcard с верхней и нижней границей дает дополнительные фичи, связанные с безопасным использованием типов. Из одного типа переменных можно только читать, в другой — только вписывать (исключением является возможность записать null для extends и прочитать Object для super ). Чтобы было легче запомнить, когда какой wildcard использовать, существует принцип PECS — Producer Extends Consumer Super. и Raw типыЕсли мы опустим указание типа, например, как здесь: Если мы попытаемся вызвать параметризованный метода у Raw типа, то компилятор выдаст нам предупреждение «Unchecked call». Если мы попытаемся выполнить присваивание ссылки на параметризованный тип Raw типу, то компилятор выдаст предупреждение «Unchecked assignment». Игнорирование этих предупреждений, как мы увидим позже, может привести к ошибкам во время выполнения нашего приложения. Wildcard CaptureПопробуем теперь реализовать метод, выполняющий перестановку элементов списка в обратном порядке. Более подробно о Wildcard Capture можно прочитать здесь и здесь. ВыводПеременные типаВот еще пример из класса Enum: Multiple bounds (множественные ограничения)ВыводПеременная типа может быть ограничена только сверху одним или несколькими типами. В случае множественного ограничения левая граница (первое ограничение) используется в процессе затирания (Type Erasure). Type ErasureНа скриншоте ниже два примера программы: Разница между ними в том, что слева происходит compile-time error, а справа все компилируется без ошибок. Почему? Reifiable типыПочему информация об одних типах доступна, а о других нет? Дело в том, что из-за процесса затирания типов компилятором информация о некоторых типах может быть потеряна. Если она потерялась, то такой тип будет уже не reifiable. То есть она во время выполнения недоступна. Если доступна – соответственно, reifiable. Решение не делать все обобщенные типы доступными во время выполнения — это одно из наиболее важных и противоречивых проектных решений в системе типов Java. Так сделали, в первую очередь, для совместимости с существующим кодом. За миграционную совместимость пришлось платить — полная доступность системы обобщенных типов во время выполнения невозможна. И еще одна задачка. Почему в примере ниже нельзя создать параметризованный Exception? Каждое catch выражение в try-catch проверяет тип полученного исключения во время выполнения программы (что равносильно instanceof), соответственно, тип должен быть Reifiable. Поэтому Throwable и его подтипы не могут быть параметризованы. Unchecked WarningsКомпиляция нашего приложения может выдать так называемый Unchecked Warning — предупреждение о том, что компилятор не смог корректно определить уровень безопасности использования наших типов. Это не ошибка, а предупреждение, так что его можно пропустить. Но желательно все-так исправить, чтобы избежать проблем в будущем. Heap PollutionКак мы упомянули ранее, присваивание ссылки на Raw тип переменной параметризованного типа, приводит к предупреждению «Unchecked assignment». Если мы проигнорируем его, то возможна ситуация под названием » Heap Pollution » (загрязнение кучи). Вот пример: В строке (1) компилятор предупреждает об «Unchecked assignment». Рассмотрим еще один пример: Java разрешает выполнить присваивание в строке (1). Это необходимо для обеспечения обратной совместимости. Но если мы попытаемся выполнить метод add в строке (2), то получим предупреждение Unchecked call — компилятор предупреждает нас о возможной ошибке. В самом деле, мы же пытаемся в список строк добавить целое число. ReflectionХотя при компиляции параметризованные типы подвергаются процедуре стирания (type erasure), кое-какую информацию мы можем получить с помощью Reflection. С появлением Generics класс java.lang.Class стал параметризованным. Рассмотрим вот этот код: ВыводЕсли информация о типе доступна во время выполнения программы, то такой тип называется Reifiable. К Reifiable типам относятся: примитивные типы, непараметризованные типы, параметризованные типы с неограниченным символом подстановки, Raw типы и массивы, элементы которых являются reifiable. Игнорирование Unchecked Warnings может привести к «загрязнению кучи» и ошибкам во время выполнения программы. Reflection не позволяет получить информацию о типе объекта, если он не Reifiable. Но Reflection позволяет получить информацию о типе возвращаемого методом значения, о типе аргументов метода и о типе полей класса. Type InferenceТермин можно перевести как «Вывод типа». Это возможность компилятора определять (выводить) тип из контекста. Вот пример кода: С появлением даймонд-оператора в Java 7 мы можем не указывать тип у ArrayList : Предположим у нас есть вот такой класс, который описывает связный список: Результат обобщенного метода List.nil() может быть выведен из правой части: Механизм выбора типа компилятором показывает, что аргумент типа для вызова List.nil() действительно String — это работает в JDK 7, все хорошо. Выглядит разумно, что компилятор также должен иметь возможность вывести тип, когда результат такого вызова обобщенного метода передается другому методу в качестве аргумента, например: В JDK 7 мы получили бы compile-time error. А в JDK 8 скомпилируется. Это и есть первая часть JEP-101, его первая цель — вывод типа в позиции аргумента. Единственная возможность осуществить это в версиях до JDK 8 — использовать явный аргумент типа при вызове обобщенного метода: Вторая часть JEP-101 говорит о том, что неплохо бы выводить тип в цепочке вызовов обобщенных методов, например: Но данная задача не решена до сих пор, и вряд ли в ближайшее время появится такая функция. Возможно, в будущих версиях JDK необходимость в этом исчезнет, но пока нужно указывать аргументы вручную: После выхода JEP 101 на StackOverflow появилось множество вопросов по теме. Программисты спрашивают, почему код, который выполнялся на 7-й версии, на 8-й выполняется иначе – или вообще не компилируется? Вот пример такого кода: Посмотрим на байт-код после компиляции на JDK1.8: А теперь байт-код после компиляции на JDK1.7 – то есть на Java 7: Чтобы избежать таких проблем, Oracle выпустил руководство по переходу с JDK1.7 на JDK 1.8 в котором описаны проблемы, которые могут возникнуть при переходе на новую версию Java, и то, как эти проблемы можно решить. Например если вы хотите, чтобы в коде выше после компиляции на Java 8 все работало так же, как и на Java 7, сделайте приведение типа вручную: ЗаключениеНа этом мой рассказ о Java Generics подходит к концу. Вот другие источники, которые помогут вам в освоении темы:
|