Иногда класс или метод нуждается в наложении ограничений на переменные типов. Приведем типичный пример. Нужно вычислить минимальный элемент массива:
1 2 3 4 5 6 7 8 9 10 11 |
class ArrayAlg { public static <T> T min(T[] a) // почти правильно { if(a == null || a.length == 0) return null; T smallest = a[0]; for(int i = 1; i < a.length; i++) if(smallest.compareTo(a[i]) > 0) smallest = a[i]; return smallest; } } |
Но здесь есть проблема. Взгляните на код метода min(). Переменная smallest имеет тип Т, а это означает, что она может быть объектом произвольного класса. Откуда мы знаем, имеет ли класс Т метод compareTo()?Решение заключается в том, чтобы наложить ограничение на тип Т, чтобы вместо него можно было подставлять только класс, реализующий Comparable — стандартный интерфейс с единственным методом compareTo(). Это делается добавлением ограничения (bound) переменной типа Т:
1 |
public static <T extends Comparable> T min(T[] a) . . . |
В действительности интерфейс Comparable сам является обобщенным типом. Пока мы проигнорируем эту сложность и предупреждения, которые генерирует компилятор.
Теперь обобщенный метод min() может вызываться только с массивами классов, реализующих интерфейс Comparable, таких как String, Date и тому подобнее. Вызов min() с массивом Rectangle даст ошибку во время компиляции, поскольку класс Rectangle не реализует Comparable.
В C++ вы не можете ограничить типы в параметрах шаблонов. Если программист создаст экземпляр шаблона с неправильным типом, сообщение об ошибке(часто невнятное) появится в шаблонном коде.
Вас может удивить, почему здесь используется ключевое слово extends вместо implements, весь Comparable — это интерфейс? Обозначение:
1 |
<T extends Ограничивающий_тип> |
говорит о том, что Т должен быть подтипом ограничивающего типа. Ключевое слово extends выбрано потому, что это резонное приближение концепции подтипа, и проектировщики Java не хотели добавлять в язык новое ключевое слово(такое как sub).
Переменная типа или тип подстановки могут иметь несколько ограничений. Например:
1 |
T extends Comparable & Serializable |
Ограничивающие типы разделяются знаком &, потому что запятые используются для разделения переменных типа.
Как и в наследовании Java, вы можете иметь столько интерфейсных подтипов, сколько хотите, но только один из ограничивающих типов может быть классом. Если вы используете для ограничения класс, он должен быть первым в списке ограничений.
В программе которая будет представлена ниже, метод вычисляет минимум и максимум в обобщенным массиве, возвращая Pair<T>.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import java.util.*; public class PairTest2 { public static void main(String[] args) { GregorianCalendar[] birthdays = { new GregorianCalendar(1906, Calendar.DECEMBER, 9), // G.Hoper new GregorianCalendar(1815, Calendar.DECEMBER, 10), // A. Lovelace new GregorianCalendar(1903, Calendar.DECEMBER, 3), // J. von Neumann new GregorianCalendar(1910, Calendar.DECEMBER, 22), // K.Zuse }; Pair<GregorianCalendar> mn = ArrayAlg.minmax(birthdays); System.out.println("min = " + mm.getFirst().getTime()); System.out.println("max = " + mm.getSecond().getTime()); } } class ArrayAlg { /** * Получает минимум и максимум из массива объектов типа Т. * @param а Массив объектов типа Т * @return Пара с минимальным и максимальным значениями * или null, если а пуст или равен null */ public static <T extends Comparable> Pair<T> minmax(T[] a) { if(a == null || a.length == 0) return null; T min = a[0]; T max = a[0]; for(int i = 1; i < a.length; i++) { if(min.compareTo(a[i]) > 0) min = a[i]; if(max.compareTo(a[i]) < 0) max = a[i]; } return new Pair<T>(min, max); } } |