В предыдущих примерах параметры типа могли заменяться классами любого типа. В большинстве случаев это замечательно, но иногда полезно ограничить количество типов, которые можно передавать параметру типа. Например, Вы хотите создать настраиваемый класс, который содержит метод, возвращающий среднее арифметическое элементов числового массива. Более того, хотите использовать этот класс для получения среднего арифметического элементов массивов разных числовых типов, включая целые, числа с плавающей запятой и двойной точности. Следовательно, следует задать тип элементов массива в общем виде с помощью параметра типа. Для создания такого класса можно попытаться написать код, похожий на приведенный в листинге 3.4.
Листинг 3.4. Неудачная попытка создать настраиваемый класс дли вычисления среднего арифметического элементов массива любого заданного числового типа
// Класс содержит ошибку!
class Stats {
T[] nums;
// передает конструктору ссылку на
// массив типа Т.
Stats(T[] o) {
nums = o;
}
// Возвращает тип double во всех cлучаях,
double average() {
double sum = 0.0;
for(int i=0; i < nums.length; i++)
sum += nums[i].doubleValue(); // Error!!!
return sum / nums.length;
}
}
В классе Stats метод average() пытается получить все элементы массива num, приведенные к типу double с помощью метода doubleValue (). Поскольку все числовые классы, такие как Integer и Double, являются подклассами класса Number, а в классе Number определен метод doubleValue(), этот метод доступен для всех числовых классов-оболочек. Но проблема состоит в том, что компилятор ничего не знает о Вашем намерении создавать объекты типа Stats, используя только числовые типы для замены параметра типа т. Более того, Вам нужно каким-либо способом обеспечить действительную передачу только числовых типов. Для обработки подобных ситуаций язык Java предлагает ограниченные типы (bounded types). При объявлении параметра типа Вы можете задать верхнюю границу, определяющую суперкласс, от которого должны быть унаследованы все аргументы типа. Такое ограничение устанавливается с помощью ключевого слова extends при описании параметра типа, как показано в следующей строке:
<Т extends superclass>
Приведенное объявление указывает на то, что параметр T можно заменить только типом superclass или его подклассами (производными от него классами). Таким образом, superclass задает верхнюю границу включительно.
Вы можете использовать суперкласс Number как верхнюю границу для настройки класса Stats, описанного ранее (листинг 3.5).
Листинг 3.5. Использование ограниченного типа при объявлении класса Stats
// В этой версии класса Stats, аргумент типа для
// Т должен быть Number, или производный
// от Number класс.
class Stats {
T[] nums; // массив типа Number или его подкласса
// Передает конструктору ссылку на
// массив типа Number или его подкласса.
Stats(T[] o) {
nums = o;
}
// Возвращает тип double в любом случае.
double average() {
double sum = 0.0;
for(int i=0; i < nums.length; i++)
sum += nums[i].doubleValue();
return sum / nums.length;
}
}
// Демонстрирует применение класса Stats.
class BoundsDemo {
public static void main(String args[]) {
Integer inums[] = { 1, 2, 3, 4, 5 };
Stats iob = new Stats(inums);
double v = iob.average();
System.out.println("iob average is " + v);
Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
Stats dob = new Stats(dnums);
double w = dob.average();
System.out.println("dob average is " + w);
// Эти строки не будут компилироваться,так как String
// не является подклассом суперкласса Number.
// String strs[] = { "1", "2", "3", "4", "5" };
// Stats strob = new Stats(strs);
// double x = strob.average() ;
// System.out.printlnf"strob average is " + v);
}
}
Далее приведен вывод результатов работы программы из листинга 3.5.
Average is 3.0
Average is 3.3
Обратите внимание на новое объявление класса Stats, приведенное в следующей строке: class Stats {
Поскольку теперь тип т ограничен суперклассом Number, компилятор языка Java знает, что все объекты типа т могут вызывать метод doubleValue(), определенный в суперклассе Number. Это само по себе значительное преимущество. Но кроме этого, ограничение типа т препятствует созданию нечисловых объектов типа Stats. Если удалить символы комментария из заключительных строк листинга 3.5, а затем выполнить компиляцию, Вы получите ошибки на этапе компиляции, так как тип String не является подклассом суперкласса Number.
Применение метасимвольных аргументов
Как ни полезна безопасность типов, иногда она может мешать формированию вполне приемлемых конструкций. Предположим, что в имеющийся класс Stats, описанный в предыдущем разделе, Вы хотите добавить метод sameAvg() который определяет, содержатся ли в двух объектах Stats массивы с одинаковым значением среднего арифметического, независимо от типа числовых данных массивов. Например, если один объект содержит значения 1.0, 2.0 и 3.0 типа double, а второй целые числа 1, 2 и 3, средние арифметические массивов будут одинаковы. Один из способов реализации метода sameAvg( ) — передача в класс Stats аргумента, последующее сравнение среднего арифметического этого аргумента со средним арифметическим объекта, вызвавшего метод, и возврат значения true, если средние арифметические одинаковы. Например, можно попытаться вызвать метод sameAvg(), как показано в следующем фрагменте кода:
Integer inums[] = {1, 2, 3, 4, 5 };
Double dnums[] = {1.1, 2.2, 3.3, 4.4, 5.5 };
Stats iob = new Stats(inums);
Stats dob = new Stats(dnums);
if(iob.sameAvg(dob))
System.out.println("Averages are the same.");
else
System.out.println("Averages differ.");
Поскольку Stats — настраиваемый класс, его метод sameAvg() может обрабатывать любой объект типа Stats и кажется, что создать этот метод просто. К сожалению, возникнут проблемы, как только Вы попытаетесь объявить параметр типа для класса Stats. Класс Stats — это параметризованный тип и неясно, какой же тип объявлять для параметра типа класса Stats в списке параметров метода.
Вам может показаться, что решение выглядит так, как показано в следующих строках кода, использующих T как параметр типа.
// Этот пример не будет работать!
// Определяет, равны ли средние арифметические.
boolean sameAvg(Stats ob) {
if ((average) == ob.average())
return true;
return false;
}
К сожалению, приведенный пример будет обрабатывать только те объекты класса Stats, у которых тип такой же, как у объекта, вызвавшего метод. Например, если метод вызывает объект типа Stats, параметр ob должен тоже быть типа Stats. Такой метод нельзя использовать для сравнения среднего арифметического объекта Stats со средним арифметическим объекта типа Stats. Следовательно, предложенный подход не будет работать, за исключением нескольких ситуаций, и не даст общего (т. е. универсального) решения.
Для создания универсального метода sameAvg() Вы должны использовать другую функциональную возможность средств настройки типов — мета символьный аргумент, или символьную маску (wildcard argument). Метасимвольный аргумент задается знаком ? и представляет неизвестный тип. Используя такую маску, можно описать метод sameAvg( ) так, как показано в следующем фрагменте кода:
// Определяет, равны ли средние арифметические.
// Обратите внимание на применение метасимвола.
boolean sameAvg(Stats> ob) {
if ((average) == ob.average())
return true;
return false;
}
В приведенном примере тип Stats> соответствует любому объекту типа Stats и позволяет сравнивать средние арифметические двух объектов типа Stats, как показано в листинге 3.6.
Листинг 3.6. Применение метасимвола, или символьной маски
class Stats {
T[] nums;
// массив типа Number или его подкласса
// Передает конструктору ссылку на
// массив типа Number или его подкласса.
Stats(T[] o) {
nums = o;
}
// Всегда возвращает тип double.
double average() {
double sum = 0.0;
for(int i=0; i < nums.length; i++)
sum += nums[i].doubleValue();
return sum / nums.length;
}
// Определяет, равны ли два средних арифметических.
// Обратите внимание на использование метасимвола (или маски).
boolean sameAvg(Stats> ob) {
if(average() == ob.average())
return true;
return false;
}
}
// Демонстрирует применение метасимвола.
class WildcardDemo {
public static void main(String args[]) {
Integer inums[] = { 1, 2, 3, 4, 5 };
Stats iob = new Stats(inums);
double v = iob.average();
System.out.println("iob average is " + v);
Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
Stats dob = new Stats(dnums);
double w = dob.average();
System.out.println("dob average is " + w);
Float fnums[] = { 1.0F, 2.0F, 3.0F, 4.0F, 5.0F };
Stats fob = new Stats(fnums);
double x = fob.average();
System.out.println("fob average is " + x);
// Проверяет, у каких массивов одинаковые средние арифметические.
System.out.print("Averages of iob and dob ");
if(iob.sameAvg(dob))
System.out.println("are the same.");
else
System.out.println("differ.");
System.out.print("Averages of iob and dob ");
if(iob.sameAvg(fob))
System.out.println("are the same.");
else
System.out.println("differ.");
}
}
Далее приведен вывод программы из листинга 3.6:
iob average is 3.О
dob average is 3.3
fob average is 3.0
Averages of iob and dob differ.
Averages of iob and fob are the same.
Последнее замечание: важно понять, что метасимвол не влияет на тип создаваемого объекта класса Stats. Тип определяется ключевым словом extends в объявлении класса Stats. Метасимвол, или маска, обеспечивает совместимость любых допустимых объектов типа Stats.
|