Ограниченные типы

В предыдущих примерах параметры типов могли быть заменены любыми типами классов. Это подходит ко многим случаям, но иногда удобно ограничить перечень типов, передаваемых в параметрах. Например, предположим, что вы хотите создать обобщенный класс, который содержит метод, возвращающий среднее значение массива чисел. Более того, вы хотите использовать этот класс для получения среднего значения чисел, включая целые и числа с плавающей точкой одинарной и двойной точности. То есть вы хотите специфицировать тип числовых данных обобщенно, используя параметр типа. Чтобы создать такой класс, можно попробовать что-то вроде этого:

// Stats пытается (безуспешно) создать
// обобщенный класс, который вычисляет
// среднее значение массива чисел
// заданного типа.
// Класс содержит ошибку!
class Stats {
T[] nums; // nums — это массив элементов типа Т
// Передать конструктору ссылку
//на массив значений типа Т.
Stats (Т[] о) {
nums = о;
}
// Возвращает double во всех случаях.
double average () {
double sum = 0.0;
for(int i=0; i < nums.length; i++)
sum += nums[i].doubleValue (); // Ошибка!!!
return sum / nums.length;
}
}

Метод average () в Stats пытается получить double-версию каждого числа в массиве nums, вызывая doubleValue (). Поскольку все числовые классы, такие как Integer и Double, являются подклассами Number, и Number определяет метод doubleValue (), этот метод доступен всем числовым классам-оболочкам. Проблема в том, что компилятор не имеет возможности узнать, что вы намерены создавать объекты Stats, используя только числовые типы. То есть, когда вы компилируете Stats, выдается сообщение об ошибке, говорящее о том, что метод doubleValue () не известен. Чтобы решить эту проблему, вам нужен какой-то способ сообщить компилятору, что вы собираетесь передавать в параметре Т только числовые типы. Более того, необходим еще некоторый способ гарантии того, что будут передаваться только числовые типы.

Чтобы справиться с этой ситуацией, Java предлагает ограниченные типы. Когда указывается параметр типа, вы можете создать ограничение сверху, которое объявляет суперкласс, от которого все типы-аргументы должны быть унаследованы. Это достигается применением слова extends при указании типа параметра, как показано ниже:

<Т extends superclass>

Это означает, что Т может быть заменено только классом superclass, либо его подклассами. То есть superclass объявляет включающую верхнюю границу.

Вы можете использовать ограничение сверху, чтобы исправить класс Stats, показанный выше, задав верхнюю границу используемого параметра типа Number:

//В этой версии Stats тип-аргумент
// Т должен быть либо Number, либо классом,
// унаследованным от Number.
class Stats {
T[] nums; // массив Number или подклассов
// Передать конструктору ссылку на массив
// элементов Number или его подклассов.
Stats (Т[] о) {
nums = о;
}
// Возвратить 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 равно " + 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 равно " + w) ;
// Это не скомпилируется, потому что String не является
// подклассом Number.
// String strs[] = { "1", "2", "3", "4", "5" };
// Stats strob = new Stats(strs);
// double x = strob . average () ;
// System.out.println("Среднее значение strob равно " + v) ;
}
}
}

Результат работы этой программы выглядит следующим образом:

Среднее значение iob равно 3.0
Среднее значение dob равно 3.3

Обратите внимание, что Stats теперь объявлен так:

class Stats {

Поскольку тип Т теперь ограничен классом Number, компилятор Java знает, что все объекты типа Т могут вызывать doubleValue (), так как это метод класса Number. Это уже серьезное преимущество. Однако в качестве дополнительного бонуса ограничение Т также предотвращает создание нечисловых объектов Stats. Например, если вы попытаетесь убрать комментарии в строках, находящихся в конце программы, и перекомпилировать ее, то получите ошибку времени компиляции, потому что String не является подклассом Number.

В дополнение к использованию типа класса как ограничения вы также можете также применять тип интерфейса. Фактически вы можете специфицировать в качестве ограничений множество интерфейсов. Более того, такое ограничение может включать как тип класса, так и один или более интерфейсов. В этом случае тип класса должен быть задан первым. Когда ограничение включает тип интерфейса, допустимы только типы-аргументы, реализующие этот интерфейс. Указывая ограничение, имеющее класс и интерфейс либо множество интерфейсов, применяйте операцию & для их объединения, например:

class Gen {
// ...

Здесь T ограничено классом по имени MyClass и интерфейсом Mylnteface. То есть любой тип, переданный в Т, должен быть подклассом MyClass и иметь реализацию Mylnteface.