Java: руководство для начинающих
Шрифт:
<Т extends суперкласс> В этом объявлении компилятору указывается, что параметр типа Т может быть заменен только суперклассом или его подклассами. Таким образом, суперкласс определяет верхнюю границу в иерархии классов Java. С помощью ограниченных типов можно устранить программные ошибки в классе NumericFns. Для этого следует указать верхнюю границу так, как показано ниже.
//В этой версии класса NumericFns аргументом типа, // заменяющим параметр типа Т, должен стать класс Number // или производный от него подкласс, как показано ниже, class NumericFns { T num; // передать конструктору ссылку на числовой объект NumericFns(Т п) { num = п; } // возвратить обратную величину double reciprocal { return 1 / num.doubleValue ; } // возвратить дробную часть double fraction { return num.doubleValue - num.intValue; } // ...
}
//
} Ниже приведен результат выполнения данной программы.
Reciprocal of iOb is 0.2 Fractional component of iOb is 0.0
Reciprocal of dOb is 0.19047619047619047 Fractional component of dOb is 0.25 Как видите, для объявления класса NumericFns в данном примере служит следующая строка кода:
class NumericFns { Теперь тип т ограничен классом Number, а следовательно, компилятору Java известно, что для всех объектов типа т доступен метод doubleValue , а также другие методы, определенные в классе Number. И хотя это само по себе дает немалые преимущества, кроме того, предотвращает создание объектов класса NumericFns для нечисловых типов. Так, если попытаться удалить комментарии из строки кода в конце рассматриваемой здесь программы, а затем повторно скомпилировать ее, то будет получено сообщение об ошибке, поскольку класс String не является подклассом, производным от класса Number. Ограниченные типы оказываются особенно полезными в тех случаях, когда нужно обеспечить совместимость одного параметра типа с другим. Рассмотрим в качестве примера представленный ниже класс Pair. В нем хранятся два объекта, которые должны быть совместимы друг с другом.
// Тип V должен совпадать с типом Т или быть его подклассом. class Pair { Т first; V second; Pair(T a, V b) { first = a; second ='b; } // ...
} В классе Pair определяются два параметра типа т и V, причем V расширяет тип Т. Это означает, что тип V должен быть либо того же типа, что и т, либо его подклассом. Благодаря такому объявлению гарантируется, что два параметра типа, передаваемые конструктору класса Pair, будут совместимы друг с другом. Например, приведенные ниже строки кода составлены правильно.
// Эта строка кода верна, так как Т и V относятся типу Integer. Paircinteger, Integer> х = new Pair(l, 2);
//И эта строка кода верна, так как Integer является подклассом Number. Pair у = new Pair(10.4, 12); А следующий фрагмент кода содержит ошибку:
// Эта строка кода недопустима, так как String не является подклассом Number. Pair z = new Pair(10.4, "12"); В данном случае класс String не является производным от класса Number, что нарушает граничное условие, указанное в объявлении класса Pair. ## Использование метасимвольных аргументов Несмотря на всю полезность типовой безопасности в обобщениях, иногда она может помешать использованию идеально подходящих языковых конструкций. Допустим, требуется реализовать метод absEqual , возвращающий логическое значение true в том случае, если два объекта рассмотренного выше класса NumericFns содержат одинаковые абсолютные значения. Допустим также, что этот метод должен оперировать любыми типами числовых данных, которые могут
NumericFns dOb = new NumericFns(1.25) ; NumericFns fOb = new NumericFns(-1.25) ;
if(dOb.absEqual(fOb)) System.out.println("Absolute values are the same."); else System.out.println("Absolute values differ."); На первый взгляд может показаться, что при выполнении метода absEqual не должно возникнуть никаких затруднений, но это совсем не так. Затруднения начнутся при первой же попытке объявить параметр типа NumericFns. Каким он должен быть? Казалось бы, подходящим должно быть следующее решение, где т указывается в качестве параметра типа:
//Не пройдет!
// определить равенство абсолютных значений в двух объектах boolean absEqual(NumericFns ob) { if(Math.abs(num.doubleValue) == Math.abs(ob.num.doubleValue) return true; return false; } В данном случае для определения абсолютного значения каждого числа используется стандартный метод Math. abs . Полученные значения сравниваются. Но дело в том, что рассматриваемое здесь решение окажется пригодным лишь в том случае, если объект класса NumericFns, передаваемый в качестве параметра, имеет тот же тип, что и текущий объект. Так, если текущий объект относится к типу NumericFns<Integer>, параметр ob также должен быть типа NumericFns<Integer>, а следовательно, сравнить текущий объект с объектом типа NumericFns<Double> не удастся. Таким образом, выбранное решение не является обобщенным. Для того чтобы создать обобщенный метод absEqual , придется воспользоваться еще одним свойством обобщений в Java, называемым метасимвольным аргументом. Для указания такого аргумента служит знак ?, обозначающий неизвестный тип данных. Используя метасимвольный аргумент, можно переписать метод absEqual следующим образом:
// определить равенство абсолютных значений в двух объектах boolean absEqual(NumericFns<?> ob) { // обратите внимание на метасимвол if(Math.abs(num.doubleValue) == Math.abs(ob.num.doubleValue) return true; return false; } В данном случае выражение NumericFns<?> соответствует любому типу объекта из класса NumericFns и позволяет сравнивать абсолютные значения в двух произвольных объектах класса NumericFns. Ниже приведен пример программы, демонстрирующий применение метасимвольного аргумента.
// Применение метасимвольного аргумента, class NumericFns { T num; // передать конструктору ссылку на числовой объект NumericFns(Т п) { num = п; } // возвратить обратную величину double reciprocal { return 1 / num.doubleValue; } // возвратить дробную часть double fraction { return num.doubleValue - num.intValue; } // определить равенство абсолютных значений в двух объектах boolean absEqual(NumericFns<?> ob) { if(Math.abs(num.doubleValue) == Math.abs(ob.num.doubleValue)) return true; return false; } // ...
} // продемонстрировать применение метасимвольного аргумента class WildcardDemo { public static void main(String args[]) { NumericFns iOb = new NumericFns(6) ; NumericFns dOb = new NumericFns(-6.0) ; NumericFns 10b = new NumericFns(5L); System.out.println("Testing iOb and dOb."); // В этом вызове метода тип метасимвольного // аргумента совпадает с типом Double. if(iOb.absEqual(dOb)) System.out.println("Absolute values are equal."); else System.out.println("Absolute values differ."); System.out.println; System.out.println("Testing iOb and 10b."); // А в этом вызове метода тип метасимвольного // аргумента совпадает с типом Long. if(iOb.absEqual(10b)) System.out.println("Absolute values are equal."); else System.out.println("Absolute values differ."); }