Выбрать главу

iOb = new Gen(88); Обратите внимание на то, что при вызове конструктора класса Gen указывается также аргумент типа Integer. Это необходимо потому, что тип объекта, на который указывает ссылка (в данном случае — iOb), должен соответствовать Gen<Integer>. Если тип ссылки, возвращаемой оператором new, будет отличаться от Gen<Integer>, возникнет ошибка при компиляции. Сообщение о такой ошибке будет, например, получено при попытке скомпилировать следующую строку кода:

iOb = new Gen(88.0); // Ошибка! Переменная iOb относится к типу Gen<Integer>, а следовательно, ее нельзя использовать для хранения ссылки на объект типа Gen<Double>. Возможность проверки на соответствие типов — одно из основных преимуществ обобщенных типов, поскольку они обеспечивают типовую безопасность. Как следует из комментариев к программе, в рассматриваемом здесь операторе присваивания

iOb = new Gen(88); производится автоупаковка целочисленного значения 88 в объект типа Integer. Это происходит потому, что обобщение Gen<Integer> создает конструктор, которому передается аргумент типа Integer. А поскольку предполагается создание объекта типа Integer, то в нем автоматически упаковывается целочисленное значение 88. Разумеется, это можно было бы явно указать в операторе присваивания, как показано ниже.

iOb = new Gen(new Integer(88)); Но в данном случае столь длинная строка кода не дает никаких преимуществ по сравнению с предыдущей, более компактной записью. Затем в программе отображается тип переменной ob в объекте iOb (в данном случае это тип Integer). А значение переменной ob получается в следующей строке кода:

int v = iOb.getobO; Метод getob () возвращает значение типа Т, замененное на Integer при объявлении переменной ссылки на объект iOb, а следовательно, метод getob () фактически возвращает значение того же самого типа Integer. Это значение автоматически распаковывается перед присваиванием переменной v типа int. И наконец, в классе GenDemo объявляется объект типа Gen<String>.

Gen strOb = new Gen("Generics Test"); В этом объявлении указывается аргумент типа String, поэтому в объекте класса Gen вместо Т подставляется тип String. В итоге создается версия класса Gen для типаString, как демонстрируют остальные строки кода рассматриваемой здесь программы. ### Действие обобщений распространяется только на объекты При определении экземпляра обобщенного класса аргумент типа, передаваемый в качестве параметра типа, должен обозначать тип класса. Для этой цели нельзя использовать простой тип, например int или char. В примере с классом Gen в качестве параметра типа Т можно передать любой класс, но не простой тип данных. Иными словами, следующее объявление недопустимо:

Gen strOb = new Gen(53); // Ошибка. Использовать простой тип нельзя! Очевидно, что запрет на использование простых типов не является серьезным ограничением, поскольку всегда можно воспользоваться классом оболочки типа, инкапсулировав в нем значение простого типа, что и было продемонстрировано в предыдущем примере программы. А поддержка в Java автоупаковки и автораспаковки еще больше упрощает применение оболочек типов в обобщениях. ### Различение обобщений по аргументам типа Для лучшего усвоения обобщенных типов следует иметь в виду, что ссылка на один вариант некоторого обобщенного типа несовместима с другим вариантом того же самого обобщенного типа. Так, если бы в рассмотренном выше примере программы присутствовала приведенная ниже строка кода, компилятор выдал бы сообщение об ошибке.

iOb = strOb; // Ошибка! Несмотря на то что обе переменные, iOb и strOb, относятся к типу Gen<T>, они являются ссылками на объекты разного типа, поскольку при их объявлении указаны разные аргументы типа. Это часть той типовой безопасности обобщений, благодаря которой предотвращаются программные ошибки. ### Обобщенный класс с двумя параметрами типа В обобщенном классе можно задать несколько параметров типа. В этом случае параметры типа разделяются запятыми. Например, приведенный ниже класс TwoGen является переделанной версией класса Gen, в которой определены два параметра типа.

// Простой обобщенный класс с двумя параметрами типа: Т и V. class TwoGen { // Применение двух параметров типа Т оb1; V оb2; // передать конструктору класса ссылки на объекты типов Т и V TwoGen(Т ol, V о2) {. ob1 = ol; оb2 = о2; } // отобразить типы Т и V void showTypes() { System.out.println("Type of T is " + obi.getClass().getName()); System.out.println("Type of V is " + ob2.getClass().getName()); } T getobl() { return obi; } V getob2() { return ob2; }

}

// продемонстрировать класс TwoGen class SimpGen { public static void main(String args[]) { // Здесь в качестве параметра типа Т передается тип // Integer, а в качестве параметра типа V - тип String. TwoGen tgObj = new TwoGencinteger, String>(88, "Generics"); // отобразить конкретные типы tgObj.showTypes(); // получить и отобразить отдельные значения int v = tgObj.getobl(); System.out.println("value: " + v); String str = tgObj.getob2(); System.out.println("value: " + str); }

} Выполнение этой программы дает следующий результат:

Type of Т is java.lang.Integer Type of V is java.lang.String value: 88 value: Generics Обратите внимание на приведенное ниже объявление класса TwoGen.

class TwoGen { Здесь определяются два параметра типа, т и V, разделяемые запятыми. А поскольку в этом классе используются два параметра типа, то при создании его объекта следует непременно указывать оба аргумента типа, как показано ниже.

TwoGen tgObj = new TwoGencinteger, String>(88, "Generics"); В данном случае тип Integer передается в качестве параметра типа т, а тип String — в качестве параметра типа V. И хотя в этом примере аргументы типа отличаются, они могут в принципе и совпадать. Например, следующая строка кода считается вполне допустимой:

TwoGen х = new TwoGen("A", "В"); В данном случае в качестве обоих параметров типа Т и V передается один и тот же тип String. Очевидно, что если аргументы типа совпадают, то определять два параметра типа в обобщенном классе нет никакой надобности. ### Общая форма обобщенного класса Синтаксис обобщений, представленных в предыдущих примерах, может быть сведен к общей форме. Ниже приведена общая форма объявления обобщенного класса.

class имякласса<списокпараметров_типа> { II ... А вот как выглядит синтаксис объявления ссылки на обобщенный класс:

имякласса<списокаргументовтипа> имяпеременной = new имякласса<списокаргументовтипа> (списокаргументов_конструктора) ; ## Ограниченные типы В предыдущих примерах параметры типа могли заменяться любым типом класса. Такая подстановка оказывается пригодной для многих целей, но иногда бывает полезно ограничить допустимый ряд типов, передаваемых в качестве параметра типа. Допустим, требуется создать обобщенный класс для хранения числовых значений и выполнения над ними различных математических операций, включая получение обратной величины или извлечение дробной части. Допустим также, что в этом классе предполагается выполнение математических операций над данными любых числовых типов: как целочисленных, так и с плавающей точкой. В таком случае будет вполне логично указывать числовой тип данных обобщенно, т.е. с помощью параметра типа. Для создания такого класса можно было бы написать код, аналогичный приведенному ниже.