дженерики что это такое программирование

PHP дженерики уже сегодня (ну, почти)

Если спросить PHP-разработчиков, какую возможность они хотят увидеть в PHP, большинство назовет дженерики.

Поддержка дженериков на уровне языка была бы наилучшим решением. Но, реализовать их сложно. Мы надеемся, что однажды нативная поддержка станет частью языка, но, вероятно, этого придется ждать несколько лет.

Данная статья покажет, как, используя существующие инструменты, в некоторых случаях с минимальными модификациями, мы можем получить мощь дженериков в PHP уже сейчас.

От переводчика: Я умышленно использую кальку с английского «дженерики», т.к. ни разу в общении не слышал, чтобы кто-то называл это «обобщенным программированием».

Содержание:

Что такое дженерики

Данный раздел покрывает краткое введение в дженерики.

Простейший пример

Так как на данный момент невозможно определить дженерики на уровне языка, нам придется воспользоваться другой прекрасной возможностью — определить их в докблоках.

Мы уже используем этот вариант во множестве проектов. Взгляните на этот пример:

Дженерики для определения ключей и значений перечисляемых типов

Выше приведен самый простой пример дженерика. Более сложные способы включают возможность указания типа его ключей, наравне с типом значений. Ниже один из способов такого описания:

Статические анализаторы, такие как Psalm, PHPStan и Phan понимают данную аннотацию и учтут ее при проверке.

Рассмотрим следующий код:

К сожалению, на момент написания статьи PhpStorm этого не умеет.

Более сложные дженерики

Продолжим углубляться в тему дженериков. Рассмотрим объект, представляющий собой стек :

Psalm и Phan поддерживают следующие аннотации:

Докблок используется для передачи дополнительной информации о типах, например:

Psalm, при анализе следующего кода:

Будет жаловаться на 2 строку с ошибкой Argument 1 of Stack::push expects User, string(hello) provided.

На данный момент PhpStorm не поддерживает данную аннотацию.

На самом деле, мы покрыли только часть информации о дженериках, но на данный момент этого достаточно.

Как внедрить дженерики без поддержки языка

Необходимо выполнить следующие действия:

Стандартизация

На данный момент, сообщество PHP уже неофициально приняло данный формат дженериков (они поддерживаются большинством инструментов и их значение понятно большинству):

Тем не менее, у нас есть проблемы с простыми примерами, вроде такого:

Psalm его понимает, и знает, какой тип у ключа и значения возвращаемого массива.

На момент написания статьи, PhpStorm этого не понимает. Используя данную запись я упускаю мощь статического анализа в реальном времени, предлагаемую PhpStorm-ом.

Если бы я выбрал Psalm как инструмент статического анализа, я бы мог написать следующее:

Psalm все это понимает.

Я предполагаю, что описанное выше соглашение будет тепло встречено большей частью PHP-сообщества. Той, которую интересуют дженерики. Тем не менее, все становится гораздо сложнее, когда речь идет о шаблонах. В настоящее время ни PHPStan, ни PhpStorm не поддерживают шаблоны. В отличие от Psalm и Phan. Их назначение схоже, но если вы копнете глубже, то поймете, что реализации немного отличаются.

Каждый из представленных вариантов является своего рода компромиссом.

Проще говоря, есть потребность в соглашении о формате записи дженериков:

Поддержка инструментами

Psalm имеет всю необходимую функциональность для проверки дженериков. Phan вроде как, тоже.

Я уверен, что PhpStorm внедрит дженерики как только в сообществе появится соглашении о едином формате.

Поддержка стороннего кода

Завершающая часть головоломки дженериков — это добавление поддержки сторонних библиотек.

Надеюсь, как только стандарт определения дженериков появится, большинство библиотек внедрят его. Тем не менее, это произойдет не сразу. Часть библиотек используются, но не имеют активной поддержки. При использовании статических анализаторов для валидации типов в дженериках важно, чтобы были определены все функции, которые принимают или возвращают эти дженерики.

Что произойдет, если ваш проект будет опираться на работу сторонних библиотек, не имеющих поддержку дженериков?

К счастью, данная проблема уже решена, и решением этим являются функции-заглушки. Psalm, Phan и PhpStorm поддерживают заглушки.

Заглушки — это обычные файлы, содержащие сигнатуры функций и методов, но не реализующие их. Добавляя докблоки в заглушки, инструменты статического анализа получают необходимую им дополнительную информацию. Например, если у вас имеется класс стека без тайпхинтов и дженериков, вроде такого.

Вы можете создать файл-заглушку, имеющую идентичные методы, но с добавлением докблоков и без реализации функций.

Когда статический анализатор видит класс стека, он берет информацию о типах из заглушки, а не из реального кода.

Возможность просто делиться кодом заглушек (например, через composer) была бы крайне полезна, т.к. позволяла бы делиться проделанной работой.

Дальнейшие шаги

Сообществу нужно отойти от соглашений и определить стандарты.

Может быть, лучшим вариантом будет PSR про дженерики?

Или, может быть, создатели основных статических анализаторов, PhpStorm, других IDE и кто-либо из людей, причастных к разработке PHP (для контроля) могли бы разработать стандарт, которым бы пользовались все.

Как только стандарт появится, все смогут помочь с добавлением дженериков в существующие библиотеки и проекты, создавая Pull Request’ы. А там, где это невозможно, разработчики могут писать и обмениваться заглушками.

Когда все будет сделано, мы сможем пользоваться инструментами вроде PhpStorm для проверки дженериков в режиме реального времени, пока пишем код. Мы можем использовать инструменты статического анализа как часть нашего CI в качестве гарантии безопасности.

Кроме того, дженерики могут быть реализованы и в PHP (ну, почти).

Ограничения

Есть ряд ограничений. PHP — это динамичный язык, который позволяет делать много «магических» вещей, например таких. Если вы используете слишком много магии PHP, может случиться так, что статические анализаторы не смогут точно извлечь все типы в системе. Если какие-либо типы неизвестны, то инструменты не смогут во всех случаях корректно использовать дженерики.

Тем не менее, основное применение подобного анализа — проверка вашей бизнес-логики. Если вы пишете чистый код, то не стоит использовать слишком много магии.

Почему бы вам просто не добавить дженерики в язык?

Это было бы наилучшим вариантом. У PHP открытый исходный код, и никто не мешает вам склонировать исходники и реализовать дженерики!

Что, если мне не нужны дженерики?

Просто игнорируйте все вышесказанное. Одно из главных преимуществ PHP в том, что он гибок в выборе подходящего уровня сложности реализации в зависимости от того, что вы создаете. С одноразовым кодом не нужно думать о таких вещах, как тайпхинтинг. А вот в больших проектах стоит использовать такие возможности.

Спасибо всем дочитавшим до этого места. Буду рад вашим замечаниям в ЛС.

UPD: ghost404 в комментариях отметил, что PHPStan с версии 0.12.x понимает psalm аннотации и поддерживает дженерики

Источник

О дженериках в PHP и о том, зачем они нам нужны

дженерики что это такое программирование. b76ba0efd41746628a098a65f996fbe2. дженерики что это такое программирование фото. дженерики что это такое программирование-b76ba0efd41746628a098a65f996fbe2. картинка дженерики что это такое программирование. картинка b76ba0efd41746628a098a65f996fbe2. Если спросить PHP-разработчиков, какую возможность они хотят увидеть в PHP, большинство назовет дженерики.

В этой статье мы рассмотрим некоторые распространённые проблемы, связанные с массивами в PHP. Все проблемы могут быть решены с помощью RFC, добавляющего в PHP дженерики. Мы не будем сильно углубляться в то, что такое дженерики, но к концу статьи вы должны понять, чем они полезны и почему многие так ждут их появления в PHP.

Допустим, у вас есть набор блог-постов, скачанных из какого-то источника данных.

Это распространённый сценарий, на примере которого мы обсудим роль дженериков и то, почему сообщество так нуждается в них. Рассмотрим проблемы, возникающие при таком сценарии.

Целостность структуры данных (Data integrity)

Если циклически пройти по нашему набору постов, то в результате получим критическую ошибку.

С целостностью структуры данных есть ещё одна сложность. Допустим, у вас есть метод, которому нужен массив блог-постов:

Но у этого подхода есть обратная сторона: вам придётся вызывать функцию применительно к распакованному массиву.

Производительность

Можно предположить, что лучше заранее знать, содержит ли массив только элементы определённого типа, чем потом в каждом цикле каждый раз вручную проверять типы.

Мы не можем прогнать на дженериках бенчмарки, потому что их пока нет, так что остаётся лишь гадать, как они повлияют на производительность. Но не будет безумием предположить, что оптимизированное поведение PHP, написанное на С, — это лучший способ решения проблемы по сравнению с созданием кучи кода для пользовательского пространства.

Автозавершение (Code completion)

Начиная с PHP 7.0 появились типы возвращаемых значений, а в PHP 7.1 они были улучшены с помощью void и типов, допускающих значение null. Но мы никак не можем сообщить IDE, что содержится в массиве. Поэтому мы возвращаемся к PHPDoc.

Неуверенность в содержимом массива и влиянии разбросанности кода на производительность и удобство сопровождения, а также неудобство написания дополнительных проверок заставили меня долго искать решение получше.

На мой взгляд, такое решение — дженерики. Не буду подробно расписывать, что они делают, об этом вы можете почитать в RFC. Но я приведу пример того, как дженерики могут помочь в решении вышеописанных проблем, всегда обеспечивая наличие в коллекции корректных данных.

Важное замечание: дженериков пока что нет в PHP. RFC предназначен для PHP 7.1, о его будущем нет никакой дополнительной информации. Нижеприведённый код основан на интерфейсах Iterator и ArrayAccess, которые существуют с PHP 5.0. В конце мы разберём пример с дженериками, представляющий собой фиктивный код.

Теперь можем воспользоваться подобным классом:

Работает! Даже без дженериков! Есть только одна проблема: решение немасштабируемое. Вам нужны отдельные реализации для каждого типа коллекции, даже если классы будут различаться только типом.

Вероятно, создавать подклассы можно с бо́льшим удобством, «злоупотребив» поздним статическим связыванием и рефлексивным API PHP. Но вам в любом случае понадобится создавать классы для каждого доступного типа.

Великолепные дженерики

И всё! Мы используем в качестве динамического типа, который можно проверять перед runtime. И опять же, класс GenericCollection можно было бы брать для любых типов.

Источник

Зачем Go нужны дженерики

Авторизуйтесь

Зачем Go нужны дженерики

Это статья о том, как введение дженериков может изменить Go и почему это будет целесообразным шагом. Здесь также будут затронуты изменения, которые придётся внести в язык для выполнения задуманного.

По отзывам пользователей, именно отсутствие дженериков — одна из главных проблем Go.

Зачем нужны дженерики?

Что же такое «добавление дженериков» и зачем это вообще нужно?

Программирование с использованием дженериков позволяет представить функции и структуры данных в обобщённой форме, с факторизацией типов.

В качестве простейшего примера предположим, что нам нужно перераспределить в обратном порядке элементы среза. Это, разумеется, не та задача, с которой часто сталкиваются программисты, но и ничего из ряда вон выходящего.

Предположим, что это целочисленный массив.

Простейшая функция, но и в ней можно обнаружить ошибку.

При определении переменной, содержащей индекс последнего элемента, надо уменьшить размер среза на 1.

Теперь создадим функцию для осуществления такой же операции с массивом строк.

В языках с динамической типизацией, таких как Python или JavaScript, функцию можно написать, не обращая внимания на определение типа элементов. На Go такой способ не сработает, поскольку это язык со статической типизацией и требует точного указания типа среза и типа его элементов. Большая часть других языков со статической типизацией, таких как C++, Java, Rust или Swift, поддерживает дженерики, чтобы устранить это затруднение.

Как Go обходится без дженериков сейчас

Интерфейсы

Создать функцию, которая может обрабатывать различные типы данных в Go можно, используя интерфейсный тип. Для этого требуется определить методы для тех типов срезов, которые вы намереваетесь передавать функции. Именно так работает функция sort.Sort из стандартной библиотеки.

Другими словами, отсутствие дженериков в Go можно компенсировать с помощью интерфейсов. Они помогают выделить общие аспекты различных типов и выразить их в качестве методов. Таким образом мы можем создавать функции, которые будут обрабатывать любые типы, поддерживающие эти методы.

Но при таком подходе приходится писать методы для интерфейсов вручную. Довольно неудобно определять отдельный тип с парой методов просто для того, чтобы перераспределить элементы среза. А поскольку методы для каждого типа будут идентичны, мы не избавились от дублирующегося кода, просто передвинули его. И хотя интерфейсы позволяют реализовать некоторые элементы дженериков, полной функциональности они не дают.

Методы по умолчанию

Ещё один способ, при котором нам не придётся самостоятельно писать методы — определение в самом языке методов по умолчанию для некоторых типов. В настоящее время такой подход не поддерживается в Go, но, к примеру, в языке можно было бы определить для каждого массива метод Index, возвращающий элемент. Однако, чтобы использовать этот способ на практике, метод должен возвращать пустой интерфейсный тип, а в таком случае мы потеряем все преимущества статического типирования. Более того, мы не смогли бы создать функцию, которая принимает два среза с элементами одного типа или map с элементами определённого типа и возвращает срез. Статическая типизация облегчает Go создание больших программ. И не хотелось бы терять это преимущество ради плюсов, которые дают дженерики.

Пакет reflect

Ещё один вариант — написать функцию Reverse с помощью пакета reflect, но это настолько неудобно и непрактично, что почти никто так не делает. Кроме того, этот подход требует чёткого определения типов и не допускает их статической проверки.

Генераторы кода

Все описанные подходы столь неудобны, что в большинстве случаев при необходимости перераспределить элементы среза просто пишут отдельные функции для каждого типа, который планируют использовать. Затем для каждой функции нужно написать и провести тесты, чтобы отловить все ошибки вроде той, что в первом примере.

Не слишком ли много лишней работы ради функций, которые отличаются только типом элемента? Должен быть способ намного лучше!

Для статически типизированных языков этот способ — дженерики. Как было сказано в самом начале, использование дженериков позволяет исключать типы, а это именно то, что нам нужно.

Что могут привнести в Go дженерики

Необходимо отметить, что под «дженериками» можно подразумевать кучу разных вещей. Например, в C++ этим термином обозначают шаблоны, которые обладают гораздо большим спектром возможностей. Однако в данной статье под дженериками подразумевается ровно то, что описано выше.

В большинстве других языков это легко реализуемо, более того, сам этот список написан с оглядкой на стандартную библиотеку шаблонов C++.

А вот список тех возможностей, которые можно реализовать именно в Go благодаря усиленной поддержке многопоточности:

Эти функции можно довольно часто встретить, при этом для каждого типа используется отдельная функция. Написать их на Go не так сложно. Но куда лучше было бы получить возможность использовать единожды написанную функцию для любых типов.

Уточним, что это всего лишь примеры, дженерики позволят проще и безопаснее создать код для куда большего количества функций.

Кроме того, как упоминалось ранее, применение дженериков затронет не только функции, но и структуры данных.

В Go встроены две основных структуры данных общего назначения: срезы и map ‘ы. Эти структуры могут содержать значения любого типа данных, со статической проверкой типа для хранящихся и запрашиваемых значений. Значения данных в этих структурах хранятся без опосредования через интерфейсы. Таким образом в []int хранятся именно целые числа, а не целые числа, преобразованные через интерфейс.

Срезы и карты — наиболее часто встречающиеся структуры данных общего назначения, но не единственные. Вот примеры других образований:

Получив возможность создавать обобщённые типы, мы сможем определять новые структуры данных, сохраняя преимущества срезов и map ‘ов: компилятор сможет проверять тип содержащихся в них значений, а сами значения можно будет хранить без преобразования с помощью интерфейсов. И к этим структурам данных можно будет применить упомянутые раньше алгоритмы.

Все эти примеры будут реализованы подобно Reverse : обобщённые функции и структуры данных единожды написанные, размещённые в пакетах и вызываемые в случае необходимости. Они должны работать как срезы и карты, то есть хранить не тип пустого интерфейса, а вполне определённые типы, которые можно проверить в процессе компиляции.

Итак, теперь вы знаете, какие преимущества получит Go от применения дженериков. Они могут предоставить нам отличную возможность унифицировать код, делиться им, упростят создание программ.

Чем придётся пожертвовать

Справедливости ради стоит сказать, что за каждое изменение в языке нужно заплатить определённую цену. И добавление дженериков в Go определённо усложнит язык. Как и в случае с любыми другими изменениями, нужно подумать о том, как максимизировать выгоду и минимизировать потери.

При разработке Go создатели ставили целью уменьшить сложность языка с помощью независимых, свободно сочетаемых особенностей. С этой целью каждая отдельная функциональность максимально упрощалась, а преимущества достигались с помощью их сочетаний. К дженерикам нужен тот же подход.

Ниже приведён список правил, которых стоит придерживаться при модификации языка:

К любым попыткам реализовать дженерики в Go нужно применять эти правила. Основная идея состоит в том, чтобы привнести в язык максимум улучшений, при этом сохранив его идентичность.

Наброски изменений

К счастью, есть способ сделать это. Во второй половине статьи мы перейдём к тому, как можно реализовать дженерики в Go.

На Gophercon 2019 Ян Лэнс Тэйлор и Роберт Гризмер обнародовали наброски предполагаемых изменений. В этом документе содержится полная информация, здесь же мы осветим основные аспекты.

Вот как будет выглядеть функция Reverse с применением дженериков:

Как видите, тело функции осталось то же, изменилась лишь сигнатура.

Тип элементов среза выделен. Теперь он называется Element и является параметром типа. Теперь это не часть параметра среза.

Для вызова функции с параметром типа в базовом случае требуется передать аргумент типа так же, как мы передаём любой другой аргумент.

К счастью, в большинстве случаев компилятор может самостоятельно вычислить аргумент типа, основываясь на типах стандартных аргументов, поэтому аргумент типа можно вообще опустить.

Вызов функции с дженериком будет выглядеть так же, как вызов любой другой.

Контракты

Поскольку Go — статически типизированный язык, требуется подробнее рассмотреть тип параметра типа. Этот мета-тип сообщает компилятору, какие разновидности типов аргументов можно передавать при вызове функции с дженериками, а также какие операции функция может производить со значениями таких типов.

Функция Reverse может работать со срезами любого типа. Единственная операция, которую эта функция осуществляет с типом Element — присвоение, а эту операцию на Go можно провести с любым типом. Для подобных распространённых функций с дженериками нам не нужно определять каких-то специфических условий для параметров.

Рассмотрим другую функцию.

Вот как для этого примера определяется контракт Sequence :

Если вы вспомните, о чём говорилось на Gophercon’е 2018, то увидите, что способ определения контрактов сильно упростился. Разработчики учли отзывы участников конвента, которым контракты образца 2018 года показались излишне сложными. Новые контракты куда проще писать, читать и понимать.

Контракты позволят определить подлежащие типы параметра типов, а также список методов параметра типов. Кроме того, они помогут описать отношения разных параметров типов.

Контракты с методами

Всё довольно просто: пройти через срез, вызвать метод String для каждого элемента и вернуть срез строк в качестве результата.

Контракты с множественными типами

Вот пример контракта со множеством параметров типа:

Важный вывод тут в том, что контракты работают не только с единичными типами, но и описывают отношения нескольких типов.

Упорядоченные типы

Хотя функцию Min легко написать самостоятельно, использование дженериков позволит разработчикам языка просто внести её в стандартную библиотеку. Вот как это может выглядеть:

Контракт Ordered говорит, что тип T должен быть упорядоченным типом, что означает поддержку таких операторов, как «меньше чем», «больше чем» и т.д.

Контракт Ordered — просто список всех упорядоченных типов, вшитых в язык. Этот контракт принимает любой из перечисленных типов, а также любой пользовательский тип, основанный на одном из перечисленных. Фактически это любой тип, к которому можно применить оператор «меньше чем».

Выходит, что гораздо проще просто перечислить все типы, поддерживающие оператор «меньше чем», чем изобретать новый параметр, подходящий для всех операторов. В конце концов в Go только вшитые типы поддерживают операторы.

Такой же подход можно использовать для любого оператора. Более того, можно написать контракт для любой функции с дженериками, работающей со встроенными типами. Это позволит создателю функции явно объявить, с какими типами сможет работать его код, а пользователю функции — определить, подходит ли она для его типов данных.

Дженерик структуры данных

Теперь давайте рассмотрим простую структуру данных на основе дженериков — двоичное дерево. В данном примере мы реализуем дерево с функцией сравнения, поэтому требований по типу элементов не будет.

Неэкспортированный метод возвращает указатель либо на слот, содержащий v, либо на то место в дереве, где она должна быть.

Детали тут не слишком существенны, это лишь простой пример для демонстрации того, как можно создать структуру данных с использованием дженериков.

Вот код, предназначенный для проверки того, содержит ли дерево значение:

А этот код добавляет новое значение:

Использовать дерево довольно просто.

Так и должно быть. Разрабатывать структуры данных с дженериками чуть сложнее, поскольку вам зачастую нужно чётко определить аргументы с типами, однако использование этого кода обычно не сложнее, чем работа с традиционными структурами данных.

Дальнейшие шаги

В настоящее время создатели языка работают над реализацией дженериков, проверяя свои идеи на практике. Процесс идёт не так быстро, как они надеялись, однако эксперименты позволяют понять, какие программы можно будет создавать с помощью их разработок.

Роберт Гризмер подготовил раннюю версию изменений пакетов Go. С ней можно потестировать проверку типов в коде с использованием дженериков и контрактов. Версия неполная и постоянно дорабатывается, но с одним пакетом работает неплохо.

Разработчики хотели бы, чтобы люди больше экспериментировали с кодом, использующим дженерики. Естественно, сначала не всё будет работать идеально, поэтому разработчики ждут отзывов и больше заинтересованы в комментариях семантики, чем деталей синтаксиса.

Цель создателей Go — добавить в язык дженерики, не усложняя языка и сохраняя его идентичность.

Источник

Пришел, увидел, обобщил: погружаемся в Java Generics

Java Generics — это одно из самых значительных изменений за всю историю языка Java. «Дженерики», доступные с Java 5, сделали использование Java Collection Framework проще, удобнее и безопаснее. Ошибки, связанные с некорректным использованием типов, теперь обнаруживаются на этапе компиляции. Да и сам язык Java стал еще безопаснее. Несмотря на кажущуюся простоту обобщенных типов, многие разработчики сталкиваются с трудностями при их использовании. В этом посте я расскажу об особенностях работы с Java Generics, чтобы этих трудностей у вас было поменьше. Пригодится, если вы не гуру в дженериках, и поможет избежать много трудностей при погружении в тему.

дженерики что это такое программирование. image loader. дженерики что это такое программирование фото. дженерики что это такое программирование-image loader. картинка дженерики что это такое программирование. картинка image loader. Если спросить PHP-разработчиков, какую возможность они хотят увидеть в PHP, большинство назовет дженерики.

Работа с коллекциями

Предположим, банку нужно подсчитать сумму сбережений на счетах клиентов. До появления «дженериков» метод вычисления суммы выглядел так:

С появлением Generics необходимость в проверке и приведении типа отпала:

Во второй строчке проверки необходимость тоже отпадала. Если потребуется, приведение типов ( casting ) будет сделано на этапе компиляции.

Принцип подстановки

ТипПодтип
NumberInteger
ListArrayList
CollectionList
IterableCollection

Примеры отношения тип/подтип

Вот пример использования принципа подстановки в Java:

Ковариантность, контравариантность и инвариантность

Но если мы попытаемся изменить содержимое массива через переменную arr и запишем туда число 42, то получим ArrayStoreException на этапе выполнения программы, поскольку 42 является не строкой, а числом. В этом недостаток ковариантности массивов Java: мы не можем выполнить проверки на этапе компиляции, и что-то может сломаться уже в рантайме.

«Дженерики» инвариантны. Приведем пример:

Wildcards

Всегда ли Generics инварианты? Нет. Приведу примеры:

Это ковариантность. List — подтип List

дженерики что это такое программирование. image loader. дженерики что это такое программирование фото. дженерики что это такое программирование-image loader. картинка дженерики что это такое программирование. картинка image loader. Если спросить PHP-разработчиков, какую возможность они хотят увидеть в PHP, большинство назовет дженерики.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

На скриншоте ниже два примера программы:
дженерики что это такое программирование. image loader. дженерики что это такое программирование фото. дженерики что это такое программирование-image loader. картинка дженерики что это такое программирование. картинка image loader. Если спросить PHP-разработчиков, какую возможность они хотят увидеть в PHP, большинство назовет дженерики.

Разница между ними в том, что слева происходит 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 подходит к концу. Вот другие источники, которые помогут вам в освоении темы:

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *