При создании коллекции можно указать тип объектов, которые будут храниться в ней.
Пример:
Напечатает “str1”.
Строка Vector < String> vect; говорит о том, что вектор будет содержать только строки. Вот почему в строке String str = vect.get(0); можно обойтись без приведения типов. При создании объекта вектора указываем параметр String: vect = new Vector < String>();.
Главная цель шаблонов – type-safety, т.е. правильное соотношение типов.
Формальные параметры
В предыдущем примере однозначно был указан параметр вектора. Есть случаи, где удобнее использовать формальный параметр, подобно формальным аргументам функций. Пример:
Создан параметризованный класс FormalVector < E>. E – формальный параметр. При создании экземпляра класса вместо E указываем конкретный тип. Простой пример:
Напечатает “str1”.
В классе StringValues создаётся экземпляр класса FormalVector < String>. Вместо формального параметра E указываем String. Теперь класс FormalVector будет хранить и работать с типом String. Можно создать экземпляр класса FormalVector с другим параметром, например, Integer: FormalVector < Integer> vect = new FormalVector < Integer>(). Это значит, что FormalVector < Integer> будет работать с типом Integer.
Все экземпляры параметризованного класса представляют один тип, независимо от фактического значения параметров.
Шаблоны и подтипы
Важное правило: если класс A есть потомок класса B, то это не означает, что aClass < A> есть потомок aClass < B>.
Методы с шаблонами
Шаблоны могут применяться и в методах. Типы аргументов можно делать параметрическими. Пример:
или так:
Рекомендуется использовать неизвестный параметр < ?> в случаях, когда от него не зависят другие аргументы и возвращаемое значение. Если такая зависимость имеется, то следует использовать формальный параметр < E>.
Возможно использовать вместе неизвестный параметр < ?> и формальный параметр < E> :
здесь аргумент list имеет ограниченный неизвестный параметр < ? extends E>, но имеется связь между двумя аргументами – это формальный параметр < E>.
Неизвестный параметр
Вместо формального параметра можно указать знак вопроса, т.е. неизвестный параметр. Изменим класс StringValues(см. выше):
Напечатает “str1”.
Новым для нас является аргумент метода
Знак вопроса вместо какого-то типа говорит о том, что здесь может оказаться любой тип, т.е. FormalVector < ?> — это супертип для любого конкретного типа, например, для FormalVector < String>. Обратим внимание на первую строку метода:
здесь str имеет тип Object. Это позволяет безопасно получать элементы коллекции методом getVal, ведь любые объекты представляют тип Object.
Неизвестный параметр может примениться и при объявлении полей:
Ограниченный неизвестный параметр
Ограниченный неизвестный параметр указывает, что вместо него можно подставить не любой тип, а только тип, наследуемый от определённого класса. Пример:
здесь на место неизвестного параметра ? можно поставить aClass или его наследников, пусть даже непрямых. aClass в этом случае называется верхней границей неизвестного параметра. Другой пример:
Параметр вида < ? super aClass> называется нижняя граница. Фактический параметр должен быть родительским по отношению к aClass, или должен быть самим aClass.
Рассмотрим формальный пример. Создадим три пустых класса: ClassA
его наследника ClassB
ClassC, как наследника от ClassB:
Почему классы пустые? В этом примере важно увидеть отношения наследования между классами, а не содержимое этих классов.
Теперь создадим класс MainClass, а в нём метод
В методе addObject имеем ограниченный неизвестный параметр < ? extends ClassA>, который говорит нам, что аргументом может быть любой вектор, содержащий объекты класса ClassA или его наследников. А таковыми и являются наши три пустых класса: ClassA, ClassB, ClassC.
В коде метода main создадим объект класса ClassA
вектор, содержащий объекты класса ClassA
добавим ca к vectA
Такой вектор соответсвует определению аргумента метода addObject, к которому мы и обращаемся:
Для классов ClassB, ClassC действия аналогичные. Вот полный код класса MainClass:
Если метод addObject объявить так:
т.е. без ограниченного неизвестного параметра, то получим сообщения об ошибках в строках addObject(vectB); и addObject(vectC);(несоответствие типов аргументов). Почему? Смотрите пункт “Шаблоны и подтипы”. Vector < ClassB> и Vector < ClassC> есть подтипы не Vector < ClassA>, а Vector < ? extends ClassA>.
Что такое raw type?
Объявляя класс-коллекцию, мы указываем параметр, т.е. тип объектов, хранящихся в нём. Если не указать параметр, то можем получить предупреждение “raw type”. Пример:
Можно объявлять коллекции без указания параметра, это сделано для работы с прежним кодом, когда ещё не было шаблонов. Ещё пример:
В последней строке получаем предупреждение, которое приведёт к ошибке при попытке извлечь строку из vect:
Сообщение об ошибке: java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String. Исправить положение можно так: указать параметр для vect, Double привести к строке
Что такое unchecked conversion?
Это значит, что компилятор не гарантирует соответствие типов. Пример:
Мы не знаем, что будет храниться в aVect, там может оказаться и не строка, отсюда предупреждение. Исправляем положение, указав параметр для aVect:
Соглашение по именам параметров
Параметрические типы принято обзначать T, если имеется несколько параметров, то для следующих используют буквы, близкие к T, т.е. S, U, V и т.д. Элементы коллекций обозначают буквой E. Ключи – K, значения — V, числа – N. Во всех случаях параметры обозначают большими буквами.