12. Обобщенные типы. Автоупаковка и автораспаковка
Last updated
Was this helpful?
Last updated
Was this helpful?
Обобщение означает параметризированный тип. Обобщения позволяют создавать классы, интерфейсы и методы, в которых тип данных указывается в виде параметра. С помощью обобщений можно создавать класс, который будет автоматически работать с разными типами данных. Такие классы, интерфейсы и методы называются обобщенными, как например, обобщенный класс или обобщенный метод.
Обобщенный код будет автоматически работать с типом данных, переданным ему в качестве параметра. Многие алгоритмы выполняются одинаково, независимо от того, к данным какого типа они будут применяться. Например, сортировка не зависит от типа данных, будь то String
, Student
или любой другой пользовательский класс, объекты которого можно сравнить между собой. Используя обобщения, можно реализовать алгоритм один раз, а затем применять его без дополнительных усилий к любому типу данных.
Рассмотрим небольшой пример. Рассмотрим класс Box
, который может содержать в себе один объект.
В данном случае наша коробка хранит объекты класса String
. Но что, если нам в нашей программе необходима коробка, которая хранила бы не только объекты класса String
, но и любого другого класса, как стандартного из библиотек Java, так и нашего созданного? Как мы можем реализовать это с помощью уже известных механизмов ООП?
Мы можем использовать механизм полиморфизма. Например, мы можем использовать класс Object
, который, как известно, является суперклассом для всех классов Java.
Второй вариант - объявить интерфейс BoxItem
и использовать его в качеcтве типа ссылочной переменной. Тогда в коробку можно будет "положить" объект класса, который реализует этот интерфейс.
Метод стал чуть более общим и может использоваться в большем количестве мест. Однако, огромный недостаток такого подхода - необходимость нисходящего преобразования извлеченного из коробки объекта. Если же мы не знаем заранее, объект какого класса будет помещен в коробку, то и нисходящее преобразование реализовать будет достаточно затруднительно.
Какой выход может быть из данной ситуации? В Java и других ОО-языках программирования существует механизм обобщенных типов. Этот механизм позволяет нам создавать классы, методы и интерфейсы, которые будут автоматически работать с типами данных, которые будут переданы позднее, при создании объекта этого класса.
Реализуем класс Box
с помощью механизма обобщенных типов
Такой класс называется обобщенным классом. Под термином обобщение следует понимать "применимость к большой группе классов". Создадим объект класса Box
После создания объекта обобщенного класса, вместо T
будет подставлен тип Item
То есть, нам нет необходимости заниматься нисходящим преобразованием и приведением типов - объект класса Box
будет работать с классом Item
.
Когда объявляется экземпляр обобщенного типа, аргумент передаваемый параметру типа должен быть ссылочным типом. Использовать для этого примитивные типы, например, int
или char
, нельзя.
Для преодоления этой проблемы в Java предусмотрен механизм автоупаковки (autoboxing) и автораспаковки (unboxing), который позволяет использовать классы-оболочки типов данных (их еще называют "обертки", wrapper).
В пятой версии Java были добавлены два очень полезных механизма - автоупаковка и автораспаковка, которые существенно упрощающих и ускоряющих создание кода, в котором приходится преобразовывать простые типа данных в объекты и наоборот.
Как мы знаем, в Java предусмотрены 8 примитивных типов данных. Примитивные типы данных имеют преимущества по сравнению с объектами, но механизм обобщенных типов и многие структуры данных в Java предполагают работу с объектами и поэтому в них нельзя хранить данные простых типов.
Для решения этой проблемы в Java предусмотрены классы-обертки (wrappers). Классы-обертки реализуются в классах Double
, Float
, Long
, Integer
, Short
, Byte
, Character
и Boolean
. Все обертки числовых типов данных являются производными от абстрактного класса Number
.
Процесс преобразования значения примитивного типа в объект соответствующего класса-обертки называется упаковкой (boxing).
Процесс извлечения значения примитивного типа из объекта-обертки называется распаковкой (unboxing).
Автоупаковка - процесс автоматической упаковки (инкапсуляции) простого типа данных в объектную обертку без необходимости явного создания объекта.
Автораспаковка - это обратный процесс автоматической распаковки (извлечения) значения, упакованного в объектную оболочку.
Количество типов, которые можно передать в обобщенный класс, не ограничено. Чтобы передать несколько типов, нужно перечислить из через запятую. Синтаксис объявления ссылки и создания такого объекта аналогичен созданию объекта с одним параметров.
Очень часто при использовании обобщений, необходимо ограничить типы, которые могу быть переданы классу. Например, при написании обобщенного класса, который выполняет операции с числами, необходимо указать, что в качестве типа можно передать тип Number
.
Для этого необходимо после placeholder написать ключевое слово extends
и указать имя класса (в этом случае будут доступны объекты этого типа или типов-наследников) или интерфейса (объекты типов, которые реализуют интерфейс).
Если класс принимает несколько типов, то мы можем указать один тип в качестве ограничителя второго. Таким образом, можно гарантировать, что оба класса совместимы друг с другом.
Для того, чтобы иметь возможность использовать обобщенный класс в качестве аргумента метода, необходимо использовать так называемый "шаблон аргумента" (wildcard).
Предположим, нужно реализовать метод, который сравнивает два объекта класса NumericValue
и возвращает true
, если оба объекта равны.
Необходимо сообщить компилятору, что входным аргументом является объект обобщенного класса. Написать NumericValue<T>
не получится, так как это не объявление класса, а записать какое-то конкретное значение мы не можем, так как мы хотим подать объект обобщенного типа с любым допустимым типом данных. Для такого случая используется специальный символ ?
, который и является шаблоном аргумента.
Такая сигнатура метода означает, что метод isEquals()
принимает на вход аргументы обобщенного типа NumericValue
, где параметр типа может быть любым допустимым для типа NumericValue
.
Кроме "ограничения сверху", мы можем устанавливать "ограничение снизу", то есть установить в качестве корректного типа этот тип и суперклассы выше по цепочке наследования. Это реализуется с помощью ключевого слова super
.
Методы в обобщенных классах могут использовать параметр типа своего класса, а следовательно, автоматически становятся обобщенными относительно параметра класса. Однако можно объявить обобщенный метод, который сам по себе использует параметр типа. Более того, такой метод может быть объявлен в обычном, а не обобщенном классе.
К примеру, реализуем методы, который будет сравнивать два массива обобщенных типов. Сначала объявим два класса
Далее объявим метод compareArrays()
Выражение T extends Comparable<T>
означает, что тип T
должен реализовывать обобщенный интерфейс Comparable<T>
, то есть чтобы мы могли сравнить объект типа T
с другим объектом типа T
. Выражение V extends T
означает, что тип V
должен быть или типом T
или производным от T
типом.
Создадим два массива и попытаемся вызвать метод compareArrays()
.
Такой код вызовет ошибку компиляции, так как тип Student
не реализует интерфейс Comparable<Student>
.
Исправим данную ошибку
Если мы перепишем класс PostGradStudent
так, чтобы он не является наследником типа Student
, то при исполнении кода получим следующую ошибку
Так как конструктор является методом, то мы также можем объявлять обобщенные конструкторы. Обобщенный конструктор можно объявить даже тогда, когда сам класс не является обобщенным.
Обобщенный интерфейс объявляется также как и обобщенный класс
Если класс реализует обобщенный интерфейс, то он также должен быть обобщенным. Не обобщенным он может быть только в том случае, если вы явно указываете тип при объявлении класса.
В интерфейсе можно указать ограничение с помощью ключевого слова extends
. Класс, который реализует обобщенный интерфейс с ограничением, также должен соблюдать эти ограничения. При объявлении класса ограничение следует прописать после названия класса, но после ключевого слова implements
его дублировать не надо.
Согласно общепринятым правилам и конвенции кода, параметры типов записываются в виде одного символа алфавита в верхнем регистре. Рекомендуется использовать следующие символы алфавита:
E (означает Element, часто используется в коллекциях);
K (означает Key);
N (означает Number);
T (означает Type);
V (означает Value);
S, U - обычно вторые, третьи, четвертые параметры типа.