Java Generics: описание и методы

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

java generics описание

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

Java Generics: описание и пример

Разберем простой пример применения обобщения к обычному классу на рисунке ниже. И уже затем приступим к детальному рассмотрению всех тонкостей и нюансов Java Generic.

generic class java

Обратите внимание на то, каким образом происходит объявление класса Pair. Сразу после имени класса открываются угловые скобки, в которых указывается буква T. Она представляет собой своеобразный заполнитель, который в процессе создания экземпляра данного класса будет заменен конкретным типом. Выглядит это следующим образом: Pair<Integer> obj = new Pair< Integer >(). Следует отметить, что вместо T можно указывать любую букву, но, как правило, используют T, V или E.

Примечание: начиная с восьмой версии Java, указав целевой тип при объявлении ссылки, угловые скобки в конструкторе можно оставить пустыми. Так приведенный выше пример можно переписать следующим образом: Pair<Integer> obj = new Pair<>().

Когда класс объявлен таким образом, далее в его теле вместо конкретных типов полей, ссылок и возвращаемых методами объектов можно использовать эту букву. Поскольку T при создании объекта класса заменяется конкретным типом, поля first и second в данном случае будут иметь тип Integer.

Следуя логике, аргументы firstItem и secondItem, передающиеся соответствующему конструктору, также должны иметь тип Integer или его подкласса. Если вы попытаетесь передать тип данных, отличающийся от того, что был указан при создании объекта, компилятор не пропустит эту ошибку. Так, конструктор с аргументами при создании объекта будет иметь следующий вид: Pair<Integer> obj = new Pair<>(new Integer(1), new Integer(2)). То же самое относится к аргументам методов setFirst и setSecond. И как вы уже, наверное, догадались, методы getFirst и getSecond будут возвращать значения типа Integer.

Обобщенный класс с несколькими параметрами типов

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

java generic

Как видим, при создании экземпляра такого класса в угловых скобках следует указывать то же количество типов, что и параметров. Если вы знакомы с таким видом структуры данных, как Map, то вы могли заметить, что там используется точно такой же принцип. Там первый аргумент определяет тип ключа, а второй – тип значения. Следует отметить, что типы передаваемых при создании объекта аргументов могут совпадать. Так, следующее объявления экземпляра класса Pair является абсолютно корректным: Pair<String, String> obj.

Некоторые особенности обобщений

Перед тем как идти далее, следует отметить, что компилятор Java не создает никаких различных версий класса Pair. На самом деле в процессе компиляции вся информация об обобщенном типе удаляется. Вместо этого выполняется приведение соответствующих типов, создавая специальную версию класса Pair. Однако в самой программе по-прежнему существует единственная обобщенная версия данного класса. Этот процесс называется в Java Generic очистка типа.

Отметим важный момент. Ссылки на разные версии одного и того же java generic класса не могут указывать на один и тот же объект. То есть, допустим, у нас есть две ссылки: Pair<Integer> obj1 и Pair<Double> obj2. Следовательно, в строке obj1 = obj2 возникнет ошибка. Хотя обе переменные относятся к типу Pair<T>, объекты, на которые они ссылаются, разные. Это яркий пример обеспечения безопасности типов в Java Generic.

Ограничения, накладываемые на обобщенные классы

Важно знать, что обобщения могут применяться только к ссылочным типам, то есть передаваемый параметру generic class java аргумент обязательно должен быть типом класса. Такие простые типы, как, например, double или long, передавать нельзя. Иными словами, следующая строка объявления класса Pair недопустима: Pair<int> obj. Тем не менее данное ограничение не представляет серьезной проблемы, так как в Java для каждого примитивного типа имеется соответствующий класс-оболочка. Строго говоря, если в классе Pair вы хотите инкапсулировать целочисленное и логическое значение, автоупаковка сделает все за вас: Pair<Integer, Boolean> obj = new Pair<>(25, true).

Еще одним серьезным ограничением является невозможность создания экземпляра параметра типа. Так, следующая строка вызовет ошибку компиляции: T first = new T(). Это очевидно, поскольку вы заранее не знаете, будет ли в качестве аргумента передаваться полноценный класс или абстрактный, или вовсе интерфейс. То же самое касается создания массивов.

Ограниченные типы

Довольно часто возникают ситуации, когда необходимо ограничить перечень типов, которые можно передавать в качестве аргумента java generic классу. Допустим, что в нашем классе Pair мы хотим инкапсулировать исключительно числовые значения для дальнейших математических операций над ними. Для этого нам необходимо задать верхнюю границу параметра типа. Реализуется это при помощи объявления суперкласса, наследуемого всеми аргументами, передающимися в угловых скобках. Выглядеть это будет следующим образом: class Pair<T extends Number>. Таким способом компилятор узнает, что вместо параметра T можно подставлять либо класс Number либо один из его подклассов.

Это распространенный прием. Такие ограничения часто используются для обеспечения совместимости параметров типа в одном и том же классе. Рассмотрим пример на нашем классе Pair: class Pair<T, V extends T>. Здесь мы сообщаем компилятору, что тип Т может быть произвольным, а тип V обязательно должен быть либо типом Т, либо одним из его подклассов.

Ограничение «снизу» происходит точно таким же образом, но вместо слова extends пишется слово super. То есть объявление class Pair<T super ArrayList> говорит о том, что вместо Т может быть подставлен либо ArrayList, либо любой класс или интерфейс, которые он наследует.

Generic методы Java и конструкторы

В Java обобщения можно применять не только в отношении классов, но и методов. Так, обобщенный метод может быть объявлен в обычном классе.

generic методы java

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

В случае конструктора все делается аналогично:

java generic очистка типа

Угловые скобки в этом случае ставятся перед названием конструктора, так как он не возвращает никакого значения. Результатом работы обеих программ будет:

Integer

String

Статья закончилась. Вопросы остались?
Комментарии 0
Подписаться
Я хочу получать
Правила публикации
Редактирование комментария возможно в течении пяти минут после его создания, либо до момента появления ответа на данный комментарий.