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

Обобщения — слишком обширная тема, чтобы подробно рассматривать ее в этой книге. Тем не менее всякий программирующий на Java должен иметь хотя бы общее представление об этом языковом средстве. На первый взгляд синтаксис обобщений может показаться непонятным, но на самом деле пользоваться ими совсем не трудно. К тому моменту, когда вы завершите проработку материала этой главы, вы не только усвоите основы обобщений, но и научитесь успешно применять их в своих программах. Основные положения об обобщениях

Термин обобщение, по существу, означает параметризированный тип. Особая роль параметризированных типов состоит в том, что они позволяют создавать классы, интерфейсы и методы, в которых обрабатываемые данные указываются в виде параметра. С помощью обобщений можно, например, создать единый класс, который автоматически становится пригодным для обработки разнотипных данных. Класс, интерфейс или метод, оперирующий параметризированным типом данных, называется обобщенным, как, например, обобщенный класс или обобщенный метод.

Главное преимущество обобщенного кода состоит в том, что он автоматически настраивается на работу с нужным типом данных. Многие алгоритмы выполняются одинаково, независимо от того, к данным какого типа они должны применяться. Например, быстрая сортировка не зависит от типа данных, в качестве которого можно использовать Integer, String, Object и даже Thread. Используя обобщения, можно реализовать алгоритм один раз, а затем применять его без особого труда к любому типу данных.

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

Но дело в том, что в таком коде трудно было соблюсти типовую безопасность, поскольку для преобразования типа Object в конкретный тип данных требовалось приведение типов. А это служило потенциальным источником ошибок из-за того, что приведение типов могло быть неумышленно выполнено неверно. Это затруднение позволяют преодолеть обобщения, обеспечивая типовую безопасность, которой раньше так недоставало. Кроме того, обобщения упрощают весь процесс, поскольку исключают необходимость выполнять приведение типов для преобразования объекта или другого типа обрабатываемых данных. Таким образом, обобщения расширяют возможности повторного использования кода и позволяют делать это надежно и просто. Простой пример обобщений

Прежде чем приступать к более подробному рассмотрению обобщений, полезно рассмотреть простой пример их применения. Ниже приведен исходный код программы, в которой объявлены два класса. Первым из них является обобщенный класс 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: