Java: руководство для начинающих (ЛП) - Шилдт Герберт
<? extends суперкласс >где после ключевого слова extends указывается суперкласс, т.е. имя класса, определяющего верхнюю границу, включая и его самого. Это означает, что в качестве аргумента допускается указывать не только подклассы данного класса, но и сам этот класс.По мере необходимости можно также указать нижнюю границу для метасимвольного аргумента. Для этой цели служит ключевое слово super, указываемое в следующей общей форме:
<? extends подкласс >В данном случае в качестве аргумента допускается использовать только суперклассы, от которых наследует подкласс, исключая его самого. Это означает, что подкласс, определяющий нижнюю границу, не относится к числу классов, передаваемых в качестве аргумента.В этом случае следующее приведение типов может быть выполнено, поскольку переменная х указывает на экземпляр класса Gen<Integer>:
(Gen) х // ДопустимоА следующее приведение типов не может быть выполнено, поскольку переменная х не указывает на экземпляр класса Gen<Long>:
(Gen) х // Недопустимо## Обобщенные методыКак было показано в предыдущих примерах, методы в обобщенных классах могут быть объявлены с параметром типа своего класса, а следовательно, такие методы автоматически становятся обобщенными относительно параметра типа. Но можно также объявить обобщенный метод с одним или несколькими параметрами его собственного типа. Более того, такой метод может быть объявлен в обычном, а не обобщенном классе.Ниже приведен пример программы, в которой объявляется класс GenericMethodDemo, не являющийся обобщенным. В этом классе объявляется статический обобщенный метод arraysEqualO, в котором определяется, содержатся ли в двух массивах одинаковые элементы, расположенные в том ж самом порядке. Такой метод можно использовать для сравнения двух массивов одинаковых или совместимых между собой типов.
// Пример простого обобщенного метода,class GenericMethodDemo {// Этот обобщенный метод определяет,// совпадает ли содержимое двух массивов.static <Т, V extends Т> boolean arraysEqual(Т[] х, V[] у) { // Если массивы имеют разную длину, они не могут быть одинаковыми, if(х.length != у.length) return false; for(int i=0; i < x.length; i++) if(!x[i].equals(y[i])) return false; // Массивы отличаются. return true; // Содержимое массивов совпадает.}public static void main(String args[]) { Integer nums[] = { 1, 2, 3, 4, 5 }; Integer nums2[] = {1, 2, 3, 4, 5 }; Integer nums3[] = {1, 2, 7, 4, 5 }; Integer nums4[] = {1, 2, 7, 4, 5, 6}; // Аргументы типа T и V неявно определяются при вызове метода. if(arraysEqual(nums, nums)) System.out.println("nums equals nums"); if(arraysEqual(nums, nums2)) System.out.println("nums equals nums2"); if(arraysEqual(nums, nums3)) System.out.println("nums equals nums3"); if(arraysEqual(nums, nums4)) System.out.println("nums equals nums4"); // создать массив объектов типа Double Double dvals[] = { 1.1, 2.2, 3.3, 4.4, 5.5 }; // Следующая строка не будет скомпилирована, так как // типы массивов nums и dvals не совпадают. // if(arraysEqual(nums, dvals)) // System.out.println("nums equals dvals");}
}Результат выполнения данной программы выглядит следующим образом:
nums equals numsnums equals nums2Рассмотрим подробнее исходный код метода arraysEqual (). Посмотрите прежде всего, как он объявляется:
static <Т, V extends Т> boolean arraysEqual(Т[] х, V[] у) {Параметры типа указываются перед возвращаемым типом. Обратите далее внимание на то, что верхней границей для типа параметра V является тип параметра Т. Таким образом, тип параметра V должен быть таким же, как и у параметра Т, или же быть его подклассом. Такая связь гарантирует, что при вызове метода arraysEqual () могут быть указаны только совместимые друг с другом параметры. И наконец, обратите внимание на то обстоятельство, что метод arraysEqual () объявлен как static, т.е. его можно вызывать независимо от любого объекта. Но обобщенные методы не обязательно должны быть статическими. В этом смысле на них не накладывается никаких ограничений.А теперь проанализируем, каким образом метод arraysEqual () вызывается в методе main (). Для этого используется обычный синтаксис, а параметры типа не указываются. И это становится возможным потому, что типы аргументов данного метода распознаются автоматически, а типы параметров Т и V настраиваются соответствующим образом. Рассмотрим в качестве примера первый вызов метода arraysEqual ():
if(arraysEqual(nums, nums))В данном случае типом первого аргумента является Integer, который и заменяет тип параметра Т. Таким же является и тип второго аргумента, а следовательно, тип параметра V также заменяется на Integer. Следовательно, выражение для вызова метода arraysEqual () составлено правильно, и оба массива можно сравнить друг с другом.Обратите далее внимание на следующие закомментированные строки:
// if(arraysEqual(nums, dvals))// System.out.println("nums equals dvals");Если удалить в них символы комментариев и попытаться скомпилировать программу, то компилятор выдаст сообщение об ошибке. Дело в том, что верхней границей для типа параметра V является тип параметра Т. Этот тип указывается после ключевого ело- ва extends, т.е. тип параметра V может быть таким же, как и у параметра т, или быть его подклассом. В данном случае типом первого аргумента рассматриваемого здесь метода является Integer, заменяющий тип параметра т, тогда как типом второго аргумента — Double, не являющийся подклассом Integer. Таким образом, вызов метода arraysEqual () оказывается недопустимым, что и приводит к ошибке при компиляции.Синтаксис объявления метода arraysEqual () может быть обобщен. Ниже приведена общая форма объявления обобщенного метода.
<параметрытипа> возвращаемыйтип имя_метода (параметры) { // ...Как и при вызове обычного метода, параметры_типа разделяются запятыми. В обобщенном методе их список предваряет возвращаемый_тип.## Обобщенные конструкторыКонструктор может быть обобщенным, даже если сам класс не является таковым. Например, в приведенной ниже программе класс Summation не является обобщенным, но в нем используется обобщенный конструктор.
// Применение обобщенного конструктора,class Summation { private int sum;// Обобщенный конструктор.<T extends Number> Summation(T arg) { sum = 0; for(int i=0; i <= arg.intValue(); i++) sum += i;}int getSum() { return sum;}
}
class GenConsDemo { public static void main(String args[]) { Summation ob = new Summation(4.0); System.out.println("Summation of 4.0 is " + ob.getSum());}
}В классе Summation вычисляется и инкапсулируется сумма всех чисел от 0 до N, причем значение N передается конструктору. Для конструктора Summation () указан параметр типа, ограниченный сверху классом Number, и поэтому объект типа Summation может быть создан с использованием любого числового типа, в том числе Integer, Float и Double. Независимо от используемого числового типа, соответствующее значение преобразуется в тип Integer при вызове intValue (), а затем вычисляется требуемая сумма. Таким образом, класс Summation совсем не обязательно объявлять обобщенным — достаточно сделать обобщенным только его конструктор.## Обобщенные интерфейсыНаряду с обобщенными классами и методами существуют также обобщенные интерфейсы. Такие интерфейсы определяются подобно обобщенным классам. Их применение демонстрируется в приведенном ниже примере программы. В ней создается интерфейс Containment, который может быть реализован классами, хранящими одно или несколько значений. Кроме того, в этой программе объявляется метод contains (), в котором определяется, содержится ли указанное значение в текущем объекте.
// Пример обобщенного интерфейса.
// В этом интерфейсе подразумевается, что реализующий// его класс содержит одно или несколько значений,interface Containment { // обобщенный интерфейс // Метод contains() проверяет, содержится ли // некоторый элемент в объекте класса, // реализующего интерфейс Containment, boolean contains(Т о);}