Простой пример обобщения

Давайте начнем с простого примера обобщенного класса. В следующей программе определены два класса. Первый — это обобщенный класс Gen, а второй — GenDemo, использующий Gen.

// Простой обобщенный класс.
// Здесь Т — это параметр типа,
// который будет заменен реальным типом
// при создании объекта типа Gen.
class Gen {
Т ob; // объявление объекта типа Т
// Передать конструктору ссылку
// на объект типа Т.
Gen(Т о) {
ob = о;
// Вернуть ob. Т getob() {
return ob;
}
// Показать тип
Т. void showType () {
System.out-println("Типом T является " + ob.getClass () .getNameO);
}
}
// Демонстрация обобщенного класса,
class GenDemo {
public static void main(String args[]) {
// Создать Gen-ссылку для Integers.
Gen iOb;
// Создать объект Gen и присвоить
// ссылку на iOb. Отметьте применение автоупаковки
// для инкапсуляции значения 88 в объект Integer.
iOb = new Gen(88);
//Показать тип данных, используемый iOb.
iOb. showType () ;
// Получить значение iOb. Обратите внимание,
// что никакого приведения не нужно,
int v = iOb. getob () ;
System.out.println("значение: " + v) ;
System.out.println();
// Создать объект Gen для String.
Gen strOb = new Gen("Обобщенный тест");
// Показать тип данных, используемый strOb.
strOb. showType () ;
// Получить значение strOb. Опять же
// приведение не требуется.
String str = strOb.getob () ;
System.out.println("Значение: " + str);
}

Результат работы этой программы:

Типом Т является java.lang.Integer
Значение: 88
Типом Т является java.lang.String
Значение: Обобщенный тест

Давайте внимательно исследуем эту программу.

Во-первых, обратите внимание на объявление Gen в следующей строке:

class Gen { Здесь Т — имя типа-параметра. Это имя используется в качестве заполнителя, куда будет подставлено имя реального типа, переданного Gen при создании реальных типов. То есть Т применяется в Gen всякий раз, когда требуется тип-параметр. Обратите внимание, что Т заключен в <>. Этот синтаксис может быть обобщен. Всякий раз, когда объявляется тип-параметр, он указывается в угловых скобках. Поскольку Gen применяет тип-параметр, Gen является обобщенным классом, который называется также параметризованным типом.

Далее Т используется для объявления объекта по имени ob, как показано ниже:

Т ob; // объявляет объект типа Т

Как объяснялось, Т — это место подстановки реального типа, который будет указан при создании объекта Gen. То есть ob будет объектом типа, переданного в т. Например, если Т передан тип String, то экземпляр ob будет типа String.

Теперь рассмотрим конструктор Gen: Gen(T о) {
ob = о;
}

Как видите, параметр о имеет тип Т. Это значит, что реальный тип о определяется типом, переданным т при создании объекта Gen. К тому же, поскольку и переменная-параметр о, и переменная-член ob имеют тип т, они обе получают одинаковый реальный тип при создании Gen.

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

Т getob () {
return ob;
}

Так как ob тоже имеет тип т, его тип совместим с типом, возвращаемым getob ().

Метод showType () отображает тип Т вызовом getName () на объекте Class, возвращенным вызовом getClass О на ob. Метод getClass () определен в Object, и потому является членом всех классов. Он возвращает объект Class, соответствующий типу класса объекта, на котором он вызван. Class определяет метод getName (), который возвращает строковое представление имени класса.

Класс GenDemo демонстрирует обобщенный класс Gen. Сначала он создает версию Gen для целых, как показано ниже:

Gen iOb;

Посмотрим на это объявление внимательней. Во-первых, отметим, что тип Integer специфицирован в угловых скобках после Gen. В этом случае Integer — это тип-аргумент, который передается в параметре типа Gen, т. Это эффективно создает версию Gen, в которой все ссылки на т транслируются в ссылки на Integer. То есть в данном объявлении ob имеет тип Integer и тип возврата getob () также имеет тип Integer.

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

Следующая строка присваивает iOb ссылку на экземпляр Integer-версию класса Gen:

iOb = new Gen(88);

Отметим, что когда вызывается конструктор Gen, аргумент типа Integer также указывается. Это необходимо, потому что типом объекта (в данном случае iOb), которому присваивается ссылка, является Gen. То есть ссылка, возвращаемая new, также должна иметь тип Gen. Если это не так, получается ошибка времени компиляции. Например, следующее присваивание вызовет ошибку компиляции:

iOb = new Gen(88 . 0) ;
// Ошибка!

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

Как указано в комментарии к программе, присваивание

iOb = new Gen(88);

использует автоупаковку для инкапсуляции значения 88, имеющего тип int, в Integer. Это работает, потому что Gen создает конструктор, принимающий аргумент Integer. Поскольку ожидается Integer, Java автоматически упаковывает 88 внутрь него. Конечно, присваивание также может быть написано явно, как здесь:

iOb = new Gen(new Integer(88));

Однако с этой версией не связано никаких преимуществ.

Программа затем отображает тип ob внутри iOb, который есть Integer. Далее программа получает значение ob в следующей строке:

int v = iOb.getob () ;

Поскольку возвращаемым типом getob () будет Т, который заменяется на Integer при объявлении iOb, то возвращаемым типом getob () также будет Integer, который автоматически распаковывается в int и присваивается переменной v, имеющей тип int. То есть нет никакой необходимости приводить тип возвращаемого значения getob () к Integer. Конечно, использовать автоупаковку не обязательно. Предыдущая строка может быть написана так:

int v = iOb.getob() .intValue ();

Однако автоупаковка позволяет сделать код более компактным. Далее в GenDemo объявляется объект типа Gen:

Gen strOb = new Gen (" Обобщенный тест");

Поскольку типом-аргументом является String, String подставляется вместо Т внутри Gen. Это создает (концептуально) String-версию Gen, что и демонстрируют остальные строки программы.