Чтение онлайн

ЖАНРЫ

Java: руководство для начинающих
Шрифт:

Прежде чем приступать к более подробному рассмотрению обобщений, полезно рассмотреть простой пример их применения. Ниже приведен исходный код программы, в которой объявлены два класса. Первым из них является обобщенный класс Gen, вторым — класс GenDemo, в котором используется класс Gen. // Простой обобщенный класс. // Здесь Т - это параметр типа, заменяемый именем // подлинного типа при создании объекта класса Gen. //В объявлении этого класса Т означает обобщенный тип. class Gen<T> { Т ob; // объявить объект типа Т // передать конструктору ссылку на объект типа Т. Gen (Т о) { ob = о; } // возвратить объект ob из метода Т getob { return ob; } // отобразить тип Т void showTypeO { System.out.println("Type of T is " + ob.getClass.getName); } } // продемонстрировать обобщенный класс class GenDemo { public static void main(String args[]) { // Создание ссылки на объект типа Gen<Integer>. Gen<Integer> iOb; // Создать объект типа Gen<Integer> и присвоить ссылку на // него переменной iOb. Обратите

внимание на автоупаковку при // инкапсуляции значения 88 в объекте типа Integer. iOb = new Gen<Integer>(88); // получить экземпляр типа Gen<Integer> // отобразить тип данных, используемых в объекте iOb iOb.showType; // Получение значения из объекта iOb. Обратите внимание // на то,что приведение типов здесь не требуется, int v = iOb.getob; System.out.println("value: " + v); System.out.println; // Создание объекта типа Gen для символьных строк. // Здесь создается ссылка и сам объект типа Gen<String>. Gen<String> strOb = new Gen<String>("Generics Test"); // отобразить тип данных, используемых в объекте strOb strOb.showType; // Получение значения из объекта strOb. //И здесь приведение типов не требуется. String str = strOb.getob; System.out.println("value: " + str) ; } }

Выполнение данной программы дает следующий результат: Type of Т is java.lang.Integer value: 88 Type of Т is java.lang.String value: Generics Test

Рассмотрим исходный код данной программы более подробно. Прежде всего обратите внимание на то, как объявляется класс Gen. Для этого используется следующая строка кода: class Gen<T> { `` где Т — имя параметра типа. Это имя служит в качестве метки-заполнителя конкретного типа, который указывается при создании объекта класса Gen. Следовательно, имя т используется в классе Gen всякий раз, когда требуется параметр типа. Обратите внимание на то, что имя т заключается в угловые скобки (< >). Этот синтаксис можно обобщить: всякий раз, когда объявляется параметр типа, он указывается в угловых скобках. А поскольку параметр типа используется в классе Gen, то такой класс считается обобщенным. В объявлении класса Gen можно указывать любое имя параметра типа, но по традиции выбирается имя Т. К числу других наиболее употребительных имен параметров типа относятся V и Е. А вообще, обозначать параметры типа рекомендуется одной прописной буквой. Далее имя т используется для объявления объекта ob, как показано в следующей строке кода:

Т ob; // объявить объект типа Т Как пояснялось выше, имя параметра типа т служит меткой-заполнителем конкретного типа, указываемого при создании объекта класса Gen. Поэтому объект ob будет иметь тип, передаваемый в качестве параметра типа т при получении экземпляра объекта класса Gen. Так, если качестве параметра типа Т указывается String, то экземпляр объекта оЪ будет отнесен к типу String. Рассмотрим далее конструктор класса Gen.

Gen(Т о) { ob = о; } Как видите, параметр о этого конструктора относится к типу Т. Это означает, что конкретный тип параметра о определяется типом, передаваемым в качестве параметра типа Т при создании объекта класса Gen. А поскольку параметр о и переменная экземпляра ob относятся к типу Т, то после создания объекта класса Gen их конкретный тип окажется одним и тем же. С помощью параметра типа т можно также указывать тип, возвращаемый методом, как показано ниже на примере метода getob .

Т getob { return ob; } Переменная экземпляра ob также относится к типу т, поэтому ее тип совпадает с типом, возвращаемым методом getob . Метод showType отображает тип Т. С этой целью метод getName вызывается для объекта типа Class, возвращаемого методом getClass , вызываемым для объекта ob. Это средство еще не применялось в представленных до сих пор примерах программ, поэтому рассмотрим его подробнее. Как пояснялось в главе 7, в классе Object определен метод getClass , автоматически являющийся членом каждого производного класса. Он возвращает объект типа Class, соответствующий типу класса текущего объекта. Класс Class относится к пакету java. lang и инкапсулирует сведения о текущем классе. В нем определено несколько методов, которые позволяют получать сведения о классах по ходу выполнения программы. К их числу принадлежит метод getName , возвращающий строковое представление имени класса. В классе Gen Demo демонстрируется применение обобщенного класса Gen. Прежде всего, в нем создается версия класса Gen для целых чисел, как показано ниже.

Gen iOb; Внимательно проанализируем это объявление. В первую очередь обратите внимание на то, что тип Integer указывается в угловых скобках после имени класса Gen. В данном случае Integer служит аргументом типа, передаваемым в качестве параметра типа Т класса Gen. В рассматриваемом здесь объявлении создается версия класса Gen, в которой тип Т заменяется типом Integer везде, где он встречается. Следовательно, после этого объявления Integer становится типом переменной ob и возвращаемым типом метода getob . Прежде чем продолжить рассмотрение обобщений, следует принять во внимание то обстоятельство, что компилятор Java на самом деле не создает разные версии Gen или другого обобщенного класса, а просто удаляет данные обобщенного типа, заменяя их приведением типов. Получаемый в итоге объект ведет себя так, как будто в программе была создана конкретная версия класса Gen. Таким

образом, в программе фактически присутствует лишь одна версия класса Gen. Процесс удаления данных обобщенного типа называется стиранием, более подробно рассматриваемым в конце этой главы. В следующей строке кода переменной iOb присваивается ссылка на экземпляр в версии класса Gen для типа Integer:

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); }

Поделиться с друзьями: