Начиная с JDK 1.5, в Java появились новые возможности для программирования. Одним из таких нововведений являются Generics. Generics являются аналогией с конструкцией «Шаблонов»(template) в С++, но имеет свои нюансы. Generics позволяют абстрагировать множество типов. Наиболее распространенными примерами являются Коллекции.

Вот типичное использование такого рода (без Generics):

Как правило, программист знает, какие данные должны быть в List’e. Тем не менее, стоит обратить особое внимание на Приведение типа («Cast») в строчке 3. Компилятор может лишь гарантировать, что метод next() вернёт Object, но чтобы обеспечить присвоение переменной типа Integer правильным и безопасным, требуется Cast. Cast не только создает беспорядки, но дает возможность появление ошибки «Runtime Error» из-за невнимательности программиста.

И появляется такой вопрос: «Как с этим бороться? » В частности: «Как же зарезервировать List для определенного типа данных?»

Как раз такую проблему решают Generics.

Обратите внимание на объявления типа для переменной myIntList. Он указывает на то, что это не просто произвольный List, а List<Integer>. Мы говорим, что List является generic-интерфейсом, который принимает параметр типа — в этом случае, Integer. Кроме того, необходимо обратить внимание на то, что теперь Cast выполняется в строчке 3 автоматически.

Некоторые могут задуматься, что беспорядок в коде увеличился, но это не так. Вместо приведения к Integer в строчке 3, у нас теперь есть Integer в качестве параметра в строчке 1. Здесь существенное отличие. Теперь компилятор может проверить этот тип на корректность во время компиляции.

И когда мы говорим, что myIntList объявлен как List<Integer>, это будет справедливо во всем коде и компилятор это гарантирует.

  • На заметку:

Эффект от Generics особенно проявляется в крупных проектах: он улучшает читаемость и надежность кода в целом.

Свойства

  • Строгая типизация
  • Единая реализация
  • Отсутствие информации о типе

Пример реализации Generic-класса

Для того чтобы использовать класс как Generics, мы должны прописать после имени класса <…>, куда можно подставить любое имя, wildcard и т.д.

После того как было объявлено имя generic-типа его можно использовать как обычный тип внутри метода. И когда в коде будет объявлен, к примеру, List<Integer>, то Е станет Integer для переменной list (как показано ниже).

Теперь рассмотрим чем старая реализация кода отличается от новой:

List<E> ─ список элементов E

Раньше :

Теперь :

Как видите, больше не нужно приводить Integer, так как метод get() возвращает ссылку на объект конкретного типа (в данном случае – Integer).

Несовместимость generic-типов

Это одна из самых важных вещей, которую вы должны узнать о Generics

Как говориться: «В бочке мёда есть ложка дегтя». Для того чтобы сохранить целостности и независимости друг от друга Коллекции, у Generics существует так называемая «Несовместимость generic-типов».

  • Суть такова:

  • Пример:

Иначе — ошибки

Проблемы реализации Generics

  • Решение 1 — Wildcard

Пусть мы захотели написать метод, который берет Collection<Object> и выводит на экран. И мы захотели вызвать dump для Integer.

  • Проблема

В этом примере List<Integer> не может использовать метод dump, так как он не является подтипом List<Object>.

Проблема в том что эта реализация кода не эффективна, так как Collection<Object> не является полностью родительской коллекцией всех остальных коллекции, грубо говоря Collection<Object> имеет ограничения.

Для решения этой проблемы используется Wildcard («?»). Он не имеет ограничения в использовании(то есть имеет соответствие с любым типом) и в этом его плюсы. И теперь, мы можем назвать это с любым типом коллекции.

  • Решение


  • Решение 2 – Bounded Wildcard

Пусть мы захотели написать метод, который рисует List<Shape>. И у Shape есть наследник Circle. И мы хотим вызвать draw для Circle.

  • Проблема

Проблема в том, что у нас не получиться из-за несовместимости типов. Предложенное решение используется, если метод который нужно реализовать использовал бы определенный тип и его подтипов. Так называемое «Ограничение сверху». Для этого нужно вместо <Shape> прописать <? extends Shape>.

  • Решение


  • Решение 3 – Generic-Метод

Пусть вы захотели сделать метод, который берет массив Object и переносить их в коллекцию.

  • Проблема

Напомним, что вы не можете просто засунуть Object в коллекции неизвестного типа. Способ решения этой проблемы является использование «Generic-Метод» Для этого перед методом нужно объявить <T> и использовать его.

  • Решение

Но все равно после выполнение останется ошибка в третьей строчке :


  • Решение 4 – Bounded type argument

Реализуем метод копирование из одной коллекции в другую

  • Проблема

Проблема в том что две Коллекции могут быть разных типов (несовместимость generic-типов). Для таких случаев было придуман Bounded type argument. Он нужен если метод ,который мы пишем использовал бы определенный тип данных. Для этого нужно ввести <N extends M> (N принимает только значения M). Также можно корректно писать <T extends A & B & C>. (Принимает значения нескольких переменных)

  • Решение


  • Решение 5 – Lower bounded wcard

Реализуем метод нахождение максимума в коллекции.

  • Проблема

  • <T extends Comparable<T>> обозначает что Т обязан реализовывать интерфейс Comparable<T>.

Ошибка возникает из за того что Test реализует интерфейс Comparable<Object>. Решение этой проблемы — Lower bounded wcard(«Ограничение снизу»). Суть в том что мы будет реализовывать метод не только для Т, но и для его Супер-типов(Родительских типов). Например: Если мы напишем

Мы можем заполнить его List<Integer>, List<Number> или List<Object>.

  • Решение


  • Решение 6 – Wildcard Capture

Реализуем метод Swap в List<?>

  • Проблема

Проблема в том, что метод List.set() не может работать с List<?>, так как ему не известно как он List. Для решение этой проблемы используют «Wildcard Capture» (или «Capture helpers»). Суть заключается в том, чтобы обмануть компилятор. Напишем еще один метод с параметризованной переменной и будем его использовать внутри нашего метода.

  • Решение

Ограничения Generic

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

  • Невозможно создать массив параметра типа

  • Невозможно создать массив Generic-классов

Преобразование типов

В Generics также можно манипулировать с информацией, хранящийся в переменных.

  • Уничтожение информации о типе

  • Добавление информации о типе

Примеры кода

  • Первый пример:

  • Второй пример:

Нахождение максимума в Коллекции Integer.

  • Без Generics:

  • С помощью Generics

<Предыдущая        Оглавление      Следующая>

Дженерики (Generics) в java обновлено: Декабрь 13, 2016 автором: admin