Настраиваемые типы (Generic Types)
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

Благодаря применению настраиваемых типов стало возможным создавать классы, интерфейсы и методы, работающие с различными типами данных, при этом обеспечивая безопасность типов. Многие алгоритмы логически идентичны вне зависимости от используемых типов данных. Например, механизм поддержки стека одинаков для стеков, хранящих элементы типа Integer, String, Object или Thread. С помощью настраиваемых типов вы можете определить один алгоритм независимо от конкретного типа данных и затем применять его без дополнительной доработки к различным типам данных. Пожалуй, включение в язык настраиваемых типов оказало наибольшее влияние на коллекции. Классы, описывающие коллекции, могут использоваться для любого типа объекта. Введение настраиваемых типов обеспечивает этим классам полную типовую безопасность (type safety). Таким образом, кроме включения в язык новой мощной функциональной возможности, применение настраиваемых типов позволяет существенно улучшить использование средств, уже существующих в языке. Именно поэтому настраиваемые типы представляют собой столь важное расширение языка Java.

Имеется:

List list= new LinkedList ();

list.add(10);

list.add(5);

list.add((Integer)list.get(0) + (Integer)list.get(1));

Хотелось бы:

List<Integer> list= new LinkedList<Integer>();

list.add(10);

list.add(5);

list.add(list.get(0) + list.get(1));

Настраиваемые типы:

• Позволяют создавать классы, интерфейсы и методы, в которых тип обрабатываемых данных задается как параметр. С помощью настраиваемых типов можно, например, создать один класс, автоматически оперирующий разными типами данных. Класс, интерфейс или метод, работающие с параметризованными типами, называются настраиваемыми (generic).

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

• Обеспечивают автоматическую проверку и приведение типов. Средства настройки типов обеспечили недостающую типовую безопасность. Они также модернизировали процесс, потому что отпала необходимость в явном приведении типов (Type cast) при переходе от типа Object к конкретному обрабатываемому типу. Благодаря введению настраиваемых типов все приведения выполняются автоматически и скрыто. Таким образом, применение настраиваемых типов расширяет возможности повторного использования кода и делает этот процесс легким и безопасным.

• Позволяют создавать хороший повторно используемый код

Пример

class Gen<T> {

T ob;

Gen(T o) {

   ob = o;

}

T getob() {

   return ob;

}

void showType() {

   System.out.println("Type of T is " + ob.getClass().getName());

}

}

class GenDemo {

public static void main(String args[]) {

   Gen<Integer> iOb;

   iOb = new Gen<Integer>(88);

   iOb.showType();

   int v = iOb.getob();

   System.out.println("value: " + v);

   System.out.println();

   Gen<String> strOb = new Gen<String>("Generics Test");

   strOb.showType();

   String str = strOb.getob();

   System.out.println("value: " + str);

}

}

Далее приведен вывод, формируемый программой.

Type of T is java.lang.Integer

value: 88

Type of T is java.lang.String

value: Generics Test

Комментарии к примеру:

Прежде всего, обратите внимание на приведенное в следующей строке объявление типа Gen.

Class Gen <T> {

Здесь T — тип, объявленный как параметр, или параметр типа (type parameter). Это имя используется как заменитель действительного типа, передаваемого в класс Gen при создании объекта. Таким образом, имя T при описании класса используется везде, где требуется параметр для типа. Обратите внимание на то, что имя T обрамляется символами: <>. Всегда при объявлении типа как параметра его имя заключается в угловые скобки. Поскольку класс Gen использует тип, объявленный как параметр, он является настраиваемым классом или параметризованным типом.

В приведенной далее строке тип T применяется для объявления объекта, названного ob.

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

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

Теперь рассмотрим конструктор класса Gen.

Gen(T о) {

ob = о;

 }

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

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

T getob() {

return ob;

}

Поскольку у объекта ob тип T, его тип совместим с типом данных, возвращаемых методом getob().

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

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

Gen<Integer> iOb;

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

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

В приведенной далее строке переменной i ob присваивается ссылка на экземпляр версии Integer класса Gen.

iOb = new Gen<Integer>(88) ;

Обратите внимание на то, что при вызове конструктора класса Gen также задается Integer — аргумент типа. Это действие необходимо, так как у объекта (В данном случае iOb), которому присваивается ссылка, тип Gen<lnteger>.

Следовательно, ссылка, возвращаемая операцией new, также должна указывать на объект типа Gen<Integer>. Если условие не соблюдается, возникнет ошибка во время компиляции, подобная приведенной далее.

iOb = new Gen<Double>(88.0); //Ошибка!

Поскольку у объекта i ob тип Gen<Integer>, он не может использоваться для ссылки на объект типа Gen<Double>. Такая проверка соответствия типов — одно из главных преимуществ настраиваемых типов, обеспечивающее типовую безопасность (type safety).

iOb = new Gen<Integer>(88);

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

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

Однако вы не получите никакого выигрыша от применения этого варианта.

Далее программа отображает на экране тип Integer — это тип переменной-члена ob из объекта iOb. Далее программа получает значение переменной ob с помощью следующей строки кода:

int v = iOb.getob();

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

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

но автораспаковка делает код короче.

Далее в классе GenDemo объявляется объект типа Gen<String>:

Gen<String> strOb = new Gen<String>("Generics Test");

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

Дата: 2019-02-19, просмотров: 209.