Краткий обзор Java Generics

Однажды в болотах FaceBook я поделилась одним из моих опубликованных постов с сообществом женщин, занимающихся исследованиями и разработками. В качестве комментария к публикации один из участников группы дал мне идею следующего поста - «Дженерики!». Не нужно спрашивать меня снова. Я всегда готов принять вызов.

Итак, краткий обзор Java Generics.

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

Java - это язык со статической типизацией, что означает, что вы должны объявить переменную с ее типом перед ее использованием. Обобщения позволяют типам быть параметрами.

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

Давайте попробуем составить «не общий» ассортиментный список, а затем добавим к нему перец.

На самом деле IntelliJ слишком умен, чтобы позволить вам попасть в это. Он предупредит вас о необработанном использовании (вызов типа без аргументов типа) параметризованного класса. И предложит вам «обобщить» Generics.java. Я подавлю предупреждения «rawtypes», чтобы мы могли видеть, что происходит.

Итак, у вас есть список специй, и теперь вы хотите извлечь из него приправу для паприки:

Хлопнуть! Ошибка! На вкус как тмин! 😖

У нас есть два способа исправить это:

  1. В частности, применительно к паприке:

2. Измените тип специи на Object (поскольку большинство классов коллекции Java получают и возвращают параметр / аргумент типа Object под капотом):

Или просто прислушайтесь к предложению IntelliJ и создайте Generics. Я же сказал, это умно! (Как вы, наверное, уже заметили, я использую (и люблю) IntelliJ, поэтому все похвалы принадлежат ему).

Добавляя ромбовидный оператор <>, содержащий тип, вы сужаете специализацию этого списка только до типа Paprika. Никогда не пытайтесь добавить какую-то другую приправу к определенному списку, вы столкнетесь с ошибкой компиляции.

Ошибка! Компилятор предложит вам изменить тип ассортиментной коробки на List<Cinnamon>.

Как видите, аналогично формальным параметрам, используемым в объявлении метода, параметры типа позволяют повторно использовать один и тот же тип (в нашем примере - List) с другими параметрами (Paprika / Cinnamon).

Разница между параметрами типа и формальными параметрами:

  • входными данными для формальных параметров являются значения;
  • входными данными для параметров типа являются типы.

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

Подобно тому, как объекты Java имеют общие типы, вы можете создавать свои собственные общие объекты (классы, интерфейсы и методы).

Класс универсального типа и интерфейс

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

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

Вышеупомянутая аналогия верна и для интерфейса.

А вот как можно реализовать интерфейс Spice с разными специями:

Универсальный класс может иметь несколько параметров типа.

Где K вводит «ключ», а V вводит «значение».

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

Общие методы

Универсальные методы - это методы, которые вводят свои собственные параметры типа.

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

Затем вы можете вызвать этот метод с аргументами разных типов:

Ограниченные универсальные шаблоны

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

Обратите внимание, что T можно заменить только типом Spice или подклассами Spice (в нашем случае паприка и корица). Таким образом, суперкласс определяет инклюзивный верхний предел.

Это означает, что, например, если Spice является подтипом Food, элемент Food нельзя добавить в ассортиментную коробку. IntelliJ вернет ошибку: «параметр типа Food находится за пределами допустимого диапазона; следует расширить Spice ».

QED! 🤓

Дженерики - действительно крутая и важная функция Java, но у нее есть список ограничений. Давайте кратко их рассмотрим.

Ограничения для дженериков

1. Невозможно создать экземпляры универсальных типов с помощью примитивных типов

Все универсальные типы реализованы как тип Object (это факт. Имейте это в виду, по крайней мере, до следующего абзаца). Следовательно, параметры типа должны быть преобразованы в Object. Поскольку примитивные типы не расширяют Object, мы не можем использовать их в качестве параметров типа. Легкий!

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

2. Невозможно использовать instanceof с параметризованными типами

Generics - это функция времени компиляции, то есть параметр типа удаляется (помните, «все типы Generics реализованы как Object»?). Вот почему вы не можете проверить, какой параметризованный тип для универсального типа используется во время выполнения:

3. Невозможно создавать, перехватывать или отбрасывать объекты параметризованных типов.

Универсальный класс не может расширять класс Throwable. Например, следующие классы не будут компилироваться.

Метод не может поймать экземпляр параметра типа. Однако вы можете использовать параметр типа в предложении throws.

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

Заключение

Я хочу резюмировать возможности Java Generics в нескольких словах: Generics - это круто!

И еще несколько слов:

Java Generics - мощное дополнение к языку Java. Это облегчает вашу жизнь и добавляет стабильности вашему коду, делая больше ошибок обнаруживаемыми во время компиляции.

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

Надеюсь, вам понравятся дженерики так же, как они мне нравятся 😊