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

// Пример простого обобщенного метода, 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 nums nums 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(Т о); }

// реализовать интерфейс Containment с помощью массива, // предназначенного для хранения значений. // Любой класс, реализующий обобщенный интерфейс, // также должен быть обобщенным. class MyClass implements Containment { T[] arrayRef; MyClass(T[] o) { arrayRef = o; } // реализовать метод contains() public boolean contains(T o) { for(T x : arrayRef) if(x.equals(o)) return true; return false; }

}

class GenlFDemo { public static void main(String args[]) { Integer x[] = { 1, 2, 3 }; MyClass<Integer> ob = new MyClass<Integer>(x); if(ob.contains(2)) System.out.println("2 is in ob"); else System.out.println("2 is NOT in ob"); if(ob.contains(5)) System.out.println("5 is in ob"); else System.out.println("5 is NOT in ob"); // Следующие строки кода недопустимы, так как объект ob // является вариантом реализации интерфейса Containment для // типа Integer, а значение 9.25 относится к типу Double. // if(ob.contains(9.25)) // Недопустимо! // System.out.println("9.25 is in ob"); ~ }

} Выполнение этой программы дает следующий результат:

2 is in ob 5 is NOT in ob Большую часть исходного кода этой программы нетрудно понять, но на некоторых ее особенностях следует все же остановиться. Обратите прежде всего внимание на то, как объявляется интерфейс Containment:

interface Containment { Обобщенные интерфейсы объявляются таким же образом, как и обобщенные классы. В данном случае параметр типа Т задает тип включаемого объекта. Интерфейс Containment реализуется классом MyClass. Объявление этого класса выглядит следующим образом:

class MyClass implements Containment { Если класс реализует обобщенный интерфейс, то он также должен быть обобщенным. В нем должен быть объявлен как минимум тот же параметр типа, который указан в объявлении интерфейса. Например, приведенный ниже вариант объявления класса MyClass недопустим.

class MyClass implements Containment { // Ошибка! В данном случае ошибка заключается в том, что в классе MyClass не объявлен параметр типа, а это означает, что передать параметр типа интерфейсу Containment нельзя. Если идентификатор Т останется неизвестным, компилятор выдаст сообщение об ошибке. Класс, реализующий обобщенный интерфейс, может не быть обобщенным только в одном случае: если при объявлении класса для интерфейса указывается конкретный тип. Такой способ объявления класса приведен ниже,