Главная страница

Java. Полное руководство. 8-е издание. С. Н. Тригуб Перевод с английского и редакция


Скачать 25.04 Mb.
НазваниеС. Н. Тригуб Перевод с английского и редакция
АнкорJava. Полное руководство. 8-е издание.pdf
Дата28.02.2017
Размер25.04 Mb.
Формат файлаpdf
Имя файлаJava. Полное руководство. 8-е издание.pdf
ТипДокументы
#3236
страница29 из 90
1   ...   25   26   27   28   29   30   31   32   ...   90
int х, у a, int b) {
x = У = b;
}
}
// Трехмерные координаты
class ThreeD extends TwoD {
int z;
ThreeD(int a, int b, int c) {
super(a, b ) ;
Глава 14. Обобщения 6 7
}
// Четырехмерные координаты FourD extends ThreeD {
int t ;
FourD(int a, int b, int c, int d) {
super(a,
b, c ) ;
t = d;
}
}
// Этот класс хранит массив координатных объектов
class Coords {
T[] coords;
C o o r d s (T [] o) { coords = o; }
}
// Демонстрация ограниченных шаблонов
class BoundedWildcard {
static void showXY(Coords c) {
System.out.p r i Координаты X Y:");
for(int i=0; i < с .coords.length; i
+
+)
System.out.println(c.coords[i].x + " " + с .coords[i ] .y);

System.out.println();
}
static void showXYZ(Coords c) { Координаты X Y Z:");

for(int i=0; i < с .coords.length; i++)
System.out.println(c.coords[i ].x + " " + су с .coords[i ].z );
System.out.printIn();
}
static void showAll(Coords c) { Координаты X Y Z T:");
for(int i=0; i < с .coords.length; i++)
System.out.println(c.coords[i ].x + " " + с .coords[i ] .y + " " + с .coords[i].z + " " + с .coords[i ].t );
System.out.println();
}
public static void main(String a r g s []) {
TwoD t d [] = {
new T w o D (0, 0),
new T w o D (7,
9) ,
new T w o D (18, 4),
new TwoD(-l, -23)
};
Coords tdlocs = new Содержимое tdlocs.");
showXY(tdlocs); // OK, это TwoD
}

3 6 8 Часть I. Язык Java
11
showXYZ(tdlocs); // Ошибка, не ThreeD
// showAll(tdlocs); // Ошибка, не FourD
// Теперь создаем несколько объектов FourD.
FourD fd[] = {
new F o u r D (1, 2, 3, 4),
new F o u r D (6, 8, 14, 8),
new F o u r D (22, 9, 4, 9),
new F o u r D (3, -2, -23, 17)
};
Coords fdlocs = new Содержимое fdlocs.");
// Здесь все ОК.
showXY(fdlocs);
showXYZ(fdlocs);
showAll(fdlocs)
Результат работы этой программы выглядит следующим образом.
Содержимое Координаты X Y :
0 О
7 9
18 4
-1 -Содержимое Координаты X Y :
1 2 б 8

22 9 Координаты X Y Z :
1 2 3 б 8 14
22 9 4
3 -2 -Координаты X Y Z Т 2 3 б 8 14 8
22 9 4 9
3 -2 -23 Обратите внимание наследующие закомментированные строки showXYZ(tdlocs); // Ошибка, не ThreeD
// showAll(tdlocs); // Ошибка, не Поскольку tdlocs
— это объект класса
Coords, он не может быть использован для вызова методов showXYZ (
) или showAll ()
, потому что ограничивающий шаблон аргумента в их объявлении предотвращает это. Чтобы убедиться в этом, попробуйте убрать комментарии с упомянутых строки попытаться скомпилировать программу. Вы получите ошибку компиляции по причине несоответствия типов.
В общем случае, для того чтобы установить верхнюю границу шаблона, используйте следующий тип шаблонного выражения extends супер кл ас о
Глава 14. Обобщения
3 6 Здесь супер класс это имя класса, который служит верхней границей. Помните, что это — включающее выражение, потому что класс, заданный в качестве верхней границы тесу пер класс, также находится в пределах допустимых типов.
Вы также можете указать нижнюю границу шаблона, добавив выражение super к объявлению шаблона. Вот его общая форма super

подклассы>
В этом случае допустимыми аргументами могут быть только классы, которые являются суперклассами для подклассы. Это исключающая конструкция, поскольку она не включает класс подклассы Создание обобщенного метода
Как было показано в предыдущих примерах, методы внутри обобщенного класса могут использовать параметр типа, а следовательно, обобщения относятся также к параметрам методов. Однако можно объявить обобщенный метод, который сам по себе использует один или более параметров типа. Более того, можно объявить обобщенный метод, который включен в параметризованный (необобщенный) класс.
Начнем с примера. В следующей программе объявлен необобщенный класс по имении статический обобщенный метод внутри класса по имени is
In
(). Метод is
In
() определяет, является ли объект членом массива. Он может быть использован с любым типом объектов и массивов до тех пор, пока массив содержит объекты, совместимые с типом искомого объекта Демонстрация простого обобщенного метода
class GenMethDemo {
// Определение, содержится ли объект в массиве < Т ,
V extends Т boolean isIn(T х, V[] у) {
for(int i=0; i < у . length; i++)
if(x.equals(y[i ])) return true;
return false;
}
public static void main(String a r g s []) {
// Применение isln() для Integer.
Integer n u m s [] = { 1, 2, 3, 4, 5 };
i f (i s I n (2, nums))
System.out.println("2 содержится вне содержится в nums");
System.out.println();
// Применение isln() для String.
String strs[] = { "один, "два, "три",
"четыре", пять (два два содержится в strs");
i f (!i s l n (семь семь содержится в strs");

3 7 Часть I. Язык Java
11
He скомпилируется!
Типы должны быть совместимыми i f (isI n два два содержится в strs "Результат работы этой программы показан ниже содержится вне содержится в два содержится в strs семь не содержится в Рассмотрим метод i s I n () поближе. Для начала посмотрите, как объявлен метод в следующей строке Т, V extends Т boolean isIn(T х, V[] у) Параметр типа объявлен перед типом возвращаемого значения метода. Тип
V ограничен сверху типом Т. То есть тип
V либо должен быть тем же типом, что и Т, либо типом его подклассов. Это отношение указывает, что метод i s I n () может быть вызван только с аргументами, совместимыми между собой. Также обратите внимание на то, что метод i s I n () статический, что позволяет вызывать его независимо от какого-либо объекта. Однако ясно, что обобщенные методы могут быть как статическими, таки нестатическими. Нет никаких ограничений на этот счет.
Теперь обратите внимание на то, как метод i s I n () вызывается внутри метода m ai n (), — с нормальным синтаксисом вызовов, без необходимости указывать аргументы типа. Дело в том, что типы аргументов подставляются автоматически, и типы Т и
V определяются соответственно. Например, в вызове (i s I n (2, num s тип первого аргумента — I n t e g e r (благодаря автоупаковке), поэтому вместо параметра Т подставляется класс I n t e g e r . Базовый тип второго аргумента — также класс I n t e g e r , что подставляет его и вместо параметра
V. Во втором вызове используются типы S t r i n g , и вместо параметров типа Т и
V подставляются классы S t r i n g . Теперь обратите внимание на закомментированный код i f (i s I n (два nums))
// два содержится в strs "Если вы уберете комментарии с этих строка затем попытаетесь скомпилировать программу, то получите ошибку. Причина в том, что тип параметра типа
V ограничен типом параметра типа Т в конструкции e x t e n d s объявления параметра типа
V. Это значит, что параметр
V должен иметь либо тип Т, либо тип его подкласса. А в этом случае первый аргумент имеет тип S t r i n g , но второй — тип
I n t e g e r , который не является подклассом класса S t r i n g . Это вызовет ошибку несоответствия типов вовремя компиляции. Такая способность обеспечивать безопасность типов — одно из наиболее существенных преимуществ обобщенных методов.
Синтаксис, использованный для создания метода i s l n ( ), можно обобщить. Вот синтаксис обобщенного метода.
<
список_параметров_типа> ссылочный_тип имя_метода
(
список_параметров)
{ / / . . Во всех случаях список_парам ет ров_т ипа — это разделенный запятыми список параметров типа. Обратите внимание на то, что для обобщенного метода список параметров типа предшествует типу возвращаемого значения
Глава 14. Обобщения
3 7 Обобщенные конструкторы
Конструкторы также могут быть обобщенными, даже если их классы таковыми не являются. Например, рассмотрим следующую короткую программу Использование обобщенного конструктора
class GenCons {
private double val;
GenCons(T arg) {
val = a r g .doubleValue();
}
void showval() {
System.out.println("val:
" + val);
}
}
class GenConsDemo {
public static void main(String a r g s []) {
GenCons test = new GenCons(100);
GenCons test2 = new GenCons(123.5F);
t e s t .showval(); Вывод этой программы 100.0
val: Поскольку конструктор
GenCons
() задает параметр обобщенного типа, который может быть подклассом класса
Number, конструктор
GenCons
() можно вызывать с любым числовым типом, включая классы
Integer, Float или
Double. Таким образом, даже несмотря на то, что класс
GenCons
— необобщенный, его конструктор обобщен.
Обобщенные интерфейсы
В дополнение к обобщенным классами методам, вы можете объявлять обобщенные интерфейсы. Обобщенные интерфейсы указывают также, как и обобщенные классы. Ниже показан пример. В нем создается интерфейс
MinMax, объявляющий методы min (
) и max ()
, которые, как ожидается, должны возвращать минимальное и максимальное значения из некоторого множества объектов Пример обобщенного интерфейса Интерфейс Min/Max.

interface MinMaxcT extends Comparable> {
T m i n ();
T m a x ();
// Реализация MinMax
class MyClasscT extends Comparable> implements MinMax {
T[] v a l s ;

3 7 2 Часть I. Язык Java
My C lass(T [] о) { vals = о }
// Возвращает минимальное значение из vals.
public Т m i n () Т v = v a l s [ 0 ] ;
for(int i=l; i < vals.length; i++)
if(vals[i ].compareTo(v) < 0) v = vals[i];
return v;
}
// Возвращает максимальное значение из vals.
public T m a x () {
T v = v a l s [0];
for(int i=l; i < vals.length; i++)
if(vals[i ].compareTo(v) > 0) v = vals[i];
return v;
}
}
class GenlFDemo {
public static void main(String a r g s []) {
Integer inums[] = {3, 6,
2 ,
8, 6 };
Character c h s [] = {'b1,
'r1,
'P1,
'
w' };
MyClass iob = new MyClass(inums);
MyClass cob = new Максимальное значение в inums: " +Минимальное значение в inums: " + Максимальное значение в chs: " + Минимальное значение в chs: " + Результат работы этой программы.
Максимальное значение в inums:
8 Минимальное значение в inums:
2 Максимальное значение в c h s :
w Минимальное значение в c h s :
Хотя большинство аспектов этой программы просты для понимания, некоторые ключевые моменты следует пояснить. В первую очередь обратите внимание на то, как объявлен интерфейс
MinMax.
interface MinMax> В общем случае обобщенный интерфейс объявляется также, как и обобщенный класс. В этом случае параметр типа Т имеет верхнюю границу — интерфейс
Comparable, определенный в пакете java. lang. lOiacc, который реализует интерфейс C om parable, определяет объекты, которые могут быть упорядочены То есть требование, чтобы параметр типа Т был ограничен сверху интерфейсом
Comparable, гарантирует, что интерфейс
MinMax может быть использован толь
Глава U . Обобщения
3 7 кос объектами, которые могут сравниваться между собой. (Более подробную информацию об интерфейсе
Comparable можно найти в главе 16.) Отметим, что интерфейс
Comparable сам по себе также является обобщенным. Он принимает параметр типа объектов, которые должны сравниваться.
Далее класс
MyClass реализует интерфейс
MinMax. Вот как выглядит объявление класса
Му С a s s.
class M y C lasscT extends Comparable> implements MinMax Обратите особое внимание на способ, которым параметр типа Т объявлен в классе
MyClass, а затем передан интерфейсу
MinMax. Поскольку интерфейс
MinMax требует типа, который реализует интерфейс
Comparable, реализующий класс (в данном случае — интерфейс
MinMax) должен указать тоже ограничение. Более того, однажды установленное это ограничение уже ненужно повторять в операторе implements. Фактически так поступать некорректно. Например, следующая строка неверна и не может быть скомпилирована Это неправильно Comparable> Однажды установленный параметр типа просто передается интерфейсу без последующих модификаций.
Вообще, если класс реализует обобщенный интерфейс, то такой класс также должен быть обобщенным, по крайней мере, в тех пределах, где он принимает параметр типа, переданный интерфейсу. Например, следующая попытка объявления класса
MyClass вызовет ошибку MyClass implements MinMax { // Неверно!

Поскольку класс
MyClass не объявляет параметра, нет способа передать его интерфейсу
MinMax. В этом случае идентификатор параметра Т просто неизвестен, и компилятор выдаст ошибку. Конечно, если, как показано ниже, класс реализует специфический тип обобщенного интерфейса MyClass implements MinMax< I n t e g e r > { / / O то реализующий класс не обязан быть обобщенным.
Обобщенный интерфейс представляет два преимущества. Во-первых, он может быть реализован для разных типов данных. Во-вторых, он позволяет включить ограничения на типы данных, для которых он может быть реализован. В примере интерфейса
MinMax вместо параметра Т могут быть подставлены только типы, совместимые с интерфейсом Вот общий синтаксис обобщенного интерфейса

имя_интерфейса<список_параметров_типа>
{ I Здесь список_парам ет ров_т ипа — разделенный запятыми список параметров типа. Когда реализуется обобщенный интерфейс, следует указать аргументы типов, как показано ниже

имя_класса<список_параметров_типа>
implements
имя_интерфейса<список_аргументов_типов>
{
Базовые типы и унаследованный код
Поскольку поддержка обобщений не существовала до JDK 5, необходим некоторый способ трансляции старого кода, разработанного до появления обобщений

3 7 4 Часть I. Язык На момент написания этой книги существует большое количество старого кода, который должен оставаться функциональными быть совместимым с обобщениями. Код, предшествующий обобщениям, должен иметь возможность работать с обобщенным кодом, и обобщенный код должен работать со старым кодом.
Чтобы облегчить переход к обобщениям, язык Java позволяет обобщенным классам быть использованными без аргументов типа. Это создает базовый шип сырой тип) для класса. Этот базовый тип совместим с унаследованным кодом, который не имеет представления об обобщенном синтаксисе. Главный недостаток использования базового типа в том, что безопасность типов утрачивается.
Вот пример, демонстрирующий базовый тип в действии Демонстрация базового типа
class Gen Т ob; // Объявление объекта типа Т Передача конструктору ссылки
/ / на объект типа Т e n То о Возврат Т g e t o b () {
return ob;
}
}
// Демонстрация базового типа
class RawDemo {
public static void main(String a r g s []) {
// Создать объект Gen для Integer.
Gen iOb = new Gen(88);
// Создать объект Gen для String.
Gen strOb = new Обобщенный тест Создать объект базового типа Gen и дать ему значение Double.
Gen raw = new Gen(new Do u b l e (98.6));
// Приведение необходимо, поскольку тип неизвестен
double d = (Double) значение " + d ) ;
// Использование базовых типов может вызвать исключения
// времени выполнения. Ниже представлены некоторые примеры Следующее приведение вызовет ошибку времени выполнения int i = (Integer) raw.getobO; // ошибка времени выполнения Это присваивание нарушает безопасность типов
strOb = raw; // OK, но потенциально неверно String str = strOb.g e t o b (); // ошибка времени выполнения Это присваивание также нарушает безопасность типов = iOb; // OK, но потенциально неверно d = (Double) raw.getobO; // ошибка времени выполнения
Глава 14. Обобщения
3 7 С этой программой связано несколько интересных моментов. Следующее объявление создает класс
Gen базового типа raw = new Gen(new Doub l e (Обратите внимание на то, что никаких аргументов типа не указывается. По сути, оператор создает объект класса
Gen, тип Т которого заменяется на тип
Obj Базовые типы не обеспечивают безопасности. То есть переменной базового типа можно присвоить ссылку на любой тип объектов класса
Gen. Обратное также возможно переменной специфического типа
Gen можно присвоить ссылку на объект базового типа
Gen. Однако обе операции потенциально небезопасны, так как механизм проверки типов при этом не применяется.
Недостаток безопасности иллюстрируется закомментированными строками в конце программы. Рассмотрим каждую из них int i = (Integer) raw.getob(); // ошибка времени выполнения

В этом операторе получается значение объекта ob внутри объекта raw, и это значение приводится к типу
Integer. Проблема в том, что объект raw содержит значение класса
Double вместо целого числа. Однако это не может быть обнаружено на этапе компиляции, поскольку тип объекта raw неизвестен. То есть этот оператор вызовет сбой вовремя выполнения.
Следующая последовательность кода присваивает объект strOb ссылка на тип
Gen) ссылке на объект класса
Gen.
strOb = raw; // ОК, но потенциально неверно String str = strOb.getob(); // ошибка времени выполнения
Это присваивание само по себе синтаксически корректно, но сомнительно. Поскольку объект strOb имеет тип
Gen, предполагается, что он содержит строку. Однако после присваивания объект, на который ссылается объект strOb, содержит значение типа
Double. То есть вовремя выполнения, когда предпринимается попытка присвоить объект strOb переменной str, происходит ошибка времени выполнения, так как объект strOb теперь содержит значение типа
Double. То есть присваивание базовой ссылки обобщенной ссылке минует механизм проверки типов.
Следующая последовательность представляет собой противоположный случайно потенциально неверно d = (Double) raw.getob(); // ошибка времени выполнения

Здесь обобщенная ссылка присваивается переменной базовой ссылки. Хотя это синтаксически корректно, но также может привести к проблемам, как показывает вторая строка. В этом случае объект raw теперь ссылается на объект, содержащий значение типа
Integer, но приведение предполагает, что в нем содержится значение типа
Double. Эта ошибка не может быть предотвращена вовремя компиляции. Вместо этого она проявляется вовремя выполнения.
Из-за того, что базовые типы представляют опасность, javac отображает непроверенные предупреждения, когда обнаруживает, что их использование может нарушить безопасность типов. В предыдущей программе следующие строки вызовут появление таких предупреждений raw = new Gen(new Do u b l e (98.6));

strOb = raw; // OK, но потенциально неверно
В первой строке происходит вызов конструктора
Gen
() без аргумента типа, что вызывает предупреждение. Во второй строке выполняется присваивание ба­

3 7 Часть I. Язык Java
зовой ссылки обобщенной переменной, что также вызывает появление предупре­
ждения.
На первый взгляд может показаться, что эта строка также должна порождать предупреждение, но этого не происходит = iOb; // OK, но потенциально неверно
Здесь не выдается никаких предупреждений компилятора, потому что присваивание не вызывает никакой дополнительной потери безопасности типов, кроме той, что уже происходит при создании объекта Один заключительный момент следует ограничивать использование базовых типов теми случаями, когда приходится смешивать унаследованный код с новым, обобщенным. Базовые типы — это просто средство переноса, а не что-то такое, что должно применяться в новом коде.
Иерархии обобщенных классов
Обобщенные классы могут быть частью иерархии классов — также, как и любые другие необобщенные классы. То есть обобщенный класс может выступать в качестве суперкласса или подкласса. Ключевое отличие между обобщенными и необобщенными иерархиями состоит в том, что в обобщенной иерархии любые аргументы типов, необходимые обобщенному суперклассу, всеми подклассами должны передаваться по иерархии вверх. Это похоже на способ, которым аргументы конструкторов передаются по иерархии.
Использование обобщенного суперкласса
Ниже приведен пример иерархии, которая использует обобщенный супер­
класс.
// Простая иерархия обобщенных классов
class Gen То о Возвращает Т g e t o b () {
return ob;
}
}
// Подкласс Gen.
class Gen2 extends Gen {
Gen2(T o) {
sup e r (о В этой иерархии класс
Gen2 расширяет обобщенный класс
Gen. Обратите внимание на то, как в следующей строке объявлен класс
Gen2.
class Gen2 extends Gen {
Глава 14. Обобщения
3 7 Параметр типа Т указан в объявлении класса
Gen2 и также передается классу
Зеп в операторе extends. Это значит, что тип, переданный классу
Gen2, будет также передан классу
Gen. Например, объявление num = new передает тип
Integer как параметр типа классу
Gen. То есть объект ob внутри части
Gen класса
Gen2 будет иметь тип
Integer. Отметим также, что класс
Gen2 никак не использует параметр типа Т, кроме того, что передает его суперклассу
Зеп. Даже если подкласс обобщенного суперкласса никак не нуждается в том, чтобы быть обобщенным, все же он должен указывать параметры типа, необходимые его обобщенному суперклассу.
Конечно, при необходимости подкласс может добавлять свои собственные параметры типа. Например, ниже показан вариант предыдущей иерархии, в котором класс
Gen2 добавляет собственный параметр типа Подкласс может добавлять собственные параметры типа
class Gen Т ob; // Объявление объекта типа Т Передача конструктору
// ссылки на объект типа То о Возвращает Т g e t o b () {
return ob;
}
}
// Подкласс Gen, который определяет
// второй параметр типа по имени V.
class Gen2 extends Gen {
V o b 2 ;
Gen2(T о, V o2) {
super(o);
ob2 = o 2 ;
}
V getob2() {
return o b 2 ;
}
}
// Создание объекта типа G e n 2 .
class HierDemo {
public static void main(String a r g s []) {
// Создание объектов Gen2 для String и Integer.
Gen2 x =
new Gen2
System.out.print(x.getob() )
;
System.out.println(x.ge t o b 2 (Обратите внимание на объявление класса
Gen2
, показанное в следующей строке Gen2 extends Gen {


3 7 Часть I. Язык Здесь Т
— тип, переданный классу
Gen, а
V
— тип, специфичный для класса
Gen2. Параметр типа
V используется при объявлении объекта, названного оЬ2
, а также в качестве типа возвращаемого значения метода getob2 ()
. В методе main (
) создается объект класса
Gen2 типа
String для параметра Т и типа
Integer для параметра
V. Программа выдает следующий вполне ожидаемый результат.
Значение равно Обобщенный подкласс
Суперклассом для обобщенного класса вполне может выступать необобщен­
ный класс. Например, рассмотрим программу Необобщенный класс может быть суперклассом

// для обобщенного подкласса Необобщенный класс
class NonGen {
int num;
NonGen(int i) {
num = i ;
}
int get n u m () {
return num;
}
}
// Обобщенный подкласс
class Gen extends NonGen {
T ob; // Объявление объекта типа T
// Передать конструктору объект
// типа То о Возвращает ob.
T g e t o b () {
return ob;
}
}
// Создать объект Gen.
class HierDemo2 {
public static void m a m ( S t r i n g args [
]
)
{
// Создать объект Gen для String.
Gen w = new Добро пожаловать, 47);
System.out.print(w.getob() + " "Результат работы этой программы показан ниже.
Добро пожаловать 47
}
Глава 14. Обобщения
3 7 Обратите внимание на то, как в этой программе класс
Gen наследуется от класса
NonGen.
class Gen extends NonGen Поскольку класс
NonGen
— необобщенный, никакие аргументы типа не указываются. То есть даже если класс
Gen объявляет параметр типа Тонне требуется и не может быть использован) классом
NonGen. То есть класс
Gen наследуется от класса
NonGen обычным способом. Никаких специальных условий не требуется.
Сравнение типов обобщенной иерархии вовремя выполнения
Вспомните оператор получения информации о типе времени выполнения — instanceof, который был описан в главе 13. Как уже объяснялось, оператор in­
stanceof определяет, является ли объект экземпляром класса. Он возвращает значение true, если объект относится к указанному типу либо может быть приведен к этому типу. Оператор instanceof может быть применим к объектам обобщенных классов. Следующий класс демонстрирует следствия совместимости типов в обобщенных иерархиях Использование оператора instanceof с иерархией обобщенных классов
class Gen То о Возвращает Т g e t o b () {
return ob;
}
}
// Подкласс Gen.
class Gen2 extends Gen {
Gen2(T o) {
super(o);
}
}
// Демонстрация определения идентификатора
// типа в иерархии обобщенных классов
class HierDemo3 {
public static void main(String a r g s []) {
// Создать объект Gen для Integer.
Gen iob = new Gen(88);
// Создать объект Gen2 для Integer.
Gen2 i0b2 = new Gen2(99);
// Создать объект Gen2 для String.
Gen2 s-tr0b2 = new Gen2 (Обобщенный тест) ;
// Проверить, является ли i0b2 какой-то из форм G e n 2 .
if(i0b2 instanceof Gen2)
System.out.p r intln("i0b2 является экземпляром Gen 2 ");

3 8 Часть I. Язык Java
// Проверить, является ли iOb2 какой-то из форм Gen.
if(iOb2 instanceof Gen)
System.out.println("iOb2 является экземпляром Gen");
System.out.println() ;
// Проверить, является ли strOb2 объектом G e n 2 .
if(strOb2 instanceof Gen2)
System.out.println("strOb2 является экземпляром Gen2");
// Проверить, является ли strOb2 объектом Gen.
if(strOb2 instanceof Gen)
System.out.println("strOb2 является экземпляром Gen");
System.out.println();
// Проверить, является ли iOb экземпляром G e n 2 ,
что не так
if(iOb instanceof Gen2)
System.out.println("iOb является экземпляром Gen2");
// Проверить, является ли iOb экземпляром Gen, что таки есть
if(iOb instanceof Gen)
System.out.println("iOb является экземпляром Gen");
// Следующее не скомпилируется,
потому что информация
// об обобщенном типе вовремя выполнения отсутствует if(iOb2 instanceof Gen2)
// System.out.println("iOb2 является экземпляром Вывод программы показан здесь является экземпляром Gen2
iOb2 является экземпляром Gen
strOb2 является экземпляром Gen2
strOb2 является экземпляром Gen
iOb является экземпляром В этой программе класс
Gen2
— подкласс класса
Gen, который обобщен по типу параметра Т. В методе main
() создается три объекта. Первый, iOb,
— объект типа
Gen. Второй, iOb2,
— объект типа
Gen2. И наконец, третий, strOb2,
— объект типа Затем программа использует оператор instanceof для проверки типа iOb2:
// Проверить, является ли iOb2 какой-то из форм G e n 2 .
if(iOb2 instanceof Gen2)
System.out.println("iOb2 является экземпляром Gen2");
// Проверить, является ли iOb2 какой-то из форм Gen.
if(iOb2 instanceof Gen)
System.out.println("iOb2 является экземпляром Как показывает вывод, обе проверки успешны. В первом случае объект
iOb2 проверяется на соответствие типу
Gen2. Эта проверка успешна, поскольку она просто подтверждает, что объект iOb2 является объектом какого-то из типов
Gen2. Применение шаблонного символа позволяет оператору instanceof определить, относится ли объект iOb2 к какому-то из типов
Gen2. Далее объект iOb проверяется на принадлежность к типу суперкласса
Gen. Это также верно, поскольку объект iOb2 представляет собой некоторую форму суперкласса
Gen.
Глава 14. Обобщения
3 8 Следующие несколько строк в методе main
() показывают туже последовательность (и некоторый результат) для объекта Далее объект iob, являющийся экземпляром суперкласса
Gene
Int eger>, проверяется в следующих строках Проверить, является ли iOb экземпляром G e n 2 ,

что не так
if(iOb instanceof Gen2)
System.out.println("iOb является экземпляром Gen 2 ");
// Проверить, является ли iOb экземпляром Gen, что таки есть
if(iOb instanceof Gen)
System.out.println("iOb является экземпляром Первый оператор if возвращает значение false, поскольку объект iob не является никакой из форм типа
Gen2
. Следующая проверка успешна, потому что объект iOb является некоторым типом объекта класса Теперь посмотрим внимательно на закомментированные строки Следующее не скомпилируется,

потому что информация
// об обобщенном типе отсутствует вовремя выполнения if(i0b2 instanceof Gen2)
// Ю Ь 2 является экземпляром Как следует из текста комментария, эти строки не компилируются, потому что они пытаются сравнить объект i0b2 со специфическим типом
Gen2
— в данном случае с типом
Gen2. Помните, что вовремя выполнения не существует никакой доступной информации о типе. Таким образом, у оператора instanceof нет способа узнать, является ли объект i0b2 экземпляром типа
Gen2 или нет.
Приведение
Вы можете приводить один экземпляр обобщенного класса к другому, только если они между собой совместимы и их аргументы типов одинаковы. Например, для предыдущей программы следующее приведение корректно) i0b2 // допустимо

потому что объект i0b2 является экземпляром типа
Gene
Int eger>. Однако следующее приведение) Ю Ь 2 // недопустимо

недопустимо, поскольку объект i0b2 не является экземпляром типа Переопределение методов в обобщенном классе

Метод обобщенного класса, как и любой другой метод, может быть переопределен. Например, рассмотрим следующую программу, в которой переопределяется метод getob ().
// Переопределение обобщенного метода в обобщенном классе
class Gen Т ob; // Объявление объекта типа Т Передать конструктору ссылку
/ / на объект типа Т e n То о

3 8 2 Часть I. Язык Java
// Возвращение Т g e t o b () {
System.out.print("getob() класса Gen: " );
return ob;
}
}
// Подкласс Gen, переопределяющий getob().
class Gen2 extends Gen {
Gen2(T o) {
super(o);
}
// Переопределение getob().
T g e t o b () {
System.out.print("getob() класса G e n 2 :
");
return ob;
}
}
// Демонстрация переопределения обобщенных методов
class OverrideDemo {
public static void main(String a r g s []) {
// Создание объекта Gen для Integer.
Gen iOb = new Gen (88);
// Создание объекта Gen2 для Integer.
Gen2 i0b2 = new Gen2 (99);
// Создание объекта Gen2 для String.
Gen2 str0b2 = new Gen2 (Обобщенный тест e t o b ());
System.out.pri n t l n (str0b2.g e t o b (Результат работы этой программы показан ниже e t o b () класса Gen: 88
g e t o b () класса G e n 2 :
99
g e t o b () класса G e n 2 :
Обобщенный тест
Как подтверждает вывод, переопределенная версия метода g e to b () вызывается для объекта класса G en2, но для объектов типа Gen вызывается версия супер­
класса.
Выведение типов и обобщения
Начиная с JDK 7 можно использовать сокращенный синтаксис для создания экземпляра обобщенного типа. Для начала рассмотрим следующий обобщенный класс MyClass
V> Т o b i ;
V ob2 ;
M y C То Глава 14. Обобщения
3 8 3
оЬ2 = о 2 ;
/ / . . До JDK 7, чтобы создать экземпляр класса
MyClass, вам следовало бы использовать оператор, подобный следующему String> mcOb =

new MyClass
String>(98, "A Здесь аргументы типа (которыми являются типы
Integer и
String) определяются дважды когда объявляется объект шсОЬ и когда экземпляр класса
MyClass создается при помощи оператора new. Со времени появления обобщений в JDK 5, эта форма была обязательна для всех версий языка Java до JDK 7. Хотя здесь нет ничего неправильного, по существу, эта форма немного более избыточна, чем это могло бы быть. В директиве new тип аргументов типа может быть без проблем выведен из типа объекта mcOb, поэтому в действительности нет никаких причин определять их во второй раз. Чтобы разрешить эту ситуацию, в комплект 7 добавлен синтаксический элемент, позволяющий избегать повторной спецификации.
В JDK 7 приведенное выше объявление может быть переписано так String> mcOb = new M y C l a s s o (98, "A Обратите внимание на то, что код создания экземпляра просто использует угловые скобки о , являющиеся пустым списком аргументов типа. Они указывают компилятору вывести аргументы типа, необходимые конструктору, в операторе new. Основное преимущество синтаксиса выведения типов в том, что он короче и иногда существенно сокращает весьма длинные операторы объявления.
Обобщим описанное выше. Когда используется выведение типов, синтаксис объявления для обобщенной ссылки и создания экземпляра имеет такую общую форму.
имя_класса< список_аргументов_типа> имя_переменной =
new
имя_класса о (список) Здесь список аргументов типа конструктора в операторе new пуст.
Выведение типов может быть также применено к передаче параметров. Например, если в класс
MyClass добавляется метод isSame(MyClasscT, V> о) {

if(obi == ото следующий вызов в JDK 7 является вполне корректным (mcOb. isSame (new MyClasso(l, "test"))) System, o u t .
println (" Same") В данном случае аргументы типа для аргумента, переданного методу isSame (), могут быть выведены.
Важно понять, что выведение типов не будет работать во всех случаях. Например, с учетом следующей иерархии классов АТ, V> {}

class ВТ, V> extends АТ, V>{ приведенное ниже объявление (которое не использует выведение типов) вполне корректно Long>, String> mc0b2 =
new MyClass
Long>,
String>(new B
L o n g > (), "Обобщения

3 8 4 Часть I. Язык Здесь, поскольку базовая ссылка на класс может ссылаться на объект производного класса, для объекта шсОЬ2 вполне допустимо ссылаться на объект класса
M y C l a s s , имеющий тип Long>,
даже притом, что ссылка имеет следующий тип Long>, Однако попытка использовать выведение типов для сокращения строки, как показано далее, не сработает Не будет работать Long>, String> mc0b2 =
new M y C l a s s o ( n e w B (), "Обобщения");
В данном случае будет получено сообщение об ошибке несоответствия типов. Поскольку синтаксис выведения типов — нововведение JDK 7 и не будет работать с устаревшими компиляторами, в примерах этой книги будет использоваться полный синтаксис объявления экземпляров обобщенных классов. Таким образом, примеры будут работать с любым компилятором Java, который поддерживает обобщения. Использование полного синтаксиса позволяет также яснее понять, что создается, а это очень важно для кода примеров, представленных в книге. Однако в собственном коде использование синтаксиса выведения типов упростит ваши объявления.
Очистка
Обычно детали того, как компилятор Java трансформирует исходный текст в объектный код, знать ненужно. Однако в случае с обобщениями некоторое общее представление о процессе важно, поскольку оно объясняет, как работает механизм обобщения и почему иногда его поведение несколько необычно. По этой причине мы приведем краткое описание того, как обобщения реализованы в языке Важное ограничение, которое было наложено на способ реализации обобщений в языке Java, заключалось в том, что необходимо было обеспечить совместимость с предыдущими версиями языка Java. Только представьте, что обобщенный код должен быть совместим со старым, не обобщенным, кодом. То есть любые изменения в синтаксисе языка Java либо в JVM должны были избегать нарушения старого кода. Способ, которым Java реализует обобщения для удовлетворения этому требованию, называется очисткой (Рассмотрим в общих чертах, как она работает. При компиляции вашего кода
Java вся информация об обобщенных типах удаляется (чистится. Это означает замену параметров типа их ограничивающим типом, которым является тип Ob j e c t , если только никакого явного ограничения не указано, с последующим использованием необходимых приведений (как того требуют аргументы типа) для поддержки совместимости типов с типами, указанными в аргументах. Компилятор также обеспечивает эту совместимость типов. Такой подход к обобщениям означает, что никакой информации о типах вовремя выполнения не существует. Это просто механизм автоматической обработки исходного кода.
Чтобы лучше понять, как работает очистка, рассмотрим два следующих класса Здесь Т ограничен типом Object по умолчанию
class Gen Т ob; // Здесь Т будет заменен Object
Глава 14. Обобщения 8 5
Gen(T о) {
ob = о Возвращает Т g e t o b () {
return ob;
}
}
// Здесь Т ограничен String,
class GenStr {
T str; // Здесь T будет заменено После того как эти классы компилируются, параметр типа Т в классе
Gen будет заменен типом
Object. Параметр типа Т в классе
GenStr будет заменен типом
String. Внутри кода классов
Gen и
GenStr для обеспечения корректной типизации применяется приведение. Например, следующая последовательность iOb = new Gen(99);

int x = будет скомпилирована, как если бы она была написана так iOb = new G e n (99);
int x = (Integer) Благодаря очистке, кое-что работает несколько иначе, чем может показаться. Например, рассмотрим короткую программу, создающую два объекта обобщенного класса
Gen.
class GenTypeDemo {
public static void main(String a r g s []) {
Gen iOb*= new Gen(99);
Gen fOb = new Gen(102.2 F Вывод этой программы показан ниже.
Gen
Gen
Как видите, типом объектов iOb и fob является типа не
Gene
Int eger> и
Gen, как вы могли ожидать. Помните, что все параметры типов вовремя компиляции удаляются. Вовремя выполнения существуют только базовые типы Зак. 303 0


3 8 6 Часть I. Язык Java
Методы-мосты
Иногда компилятору приходится добавлять к классу так называемый метод-мост
(bridge m ethod), чтобы справиться с ситуациями, когда результат очистки типов в перегруженном методе подкласса не совпадает стем, что получается при очистке в суперклассе. В этом случае создается метод, который использует очистку типов суперкласса, и этот метод вызывает соответствующий метод подкласса, выполняющий очистку. Конечно, методы-мосты появляются только на уровне кода виртуальной машины, они невидимы для вас и недоступны для непосредственного вызова.
Несмотря на то что методы-мосты — это не тов чем вы обычно нуждаетесь и с чем имеете дело, все же полезно рассмотреть ситуацию, в которой они создаются. Взгляните наследующую программу Ситуация, в которой создается метод-мост,

class Gen Т ob; // объявить объект типа Т Передать конструктору ссылку на
// объект типа То о Возвращает Т g e t o b () {
return ob;
}
}
// Подкласс Gen.
class Gen2 extends Gen {
Gen2(String o) {
super(o);
}
// Ориентированная на строки перегрузка getob().
String g e t o b () Вызван String getob(): ");
return ob;
}
}
// Демонстрация ситуации, в которой необходим метод-мост,
class BridgeDemo {
public static void main(String a r g s []) {
// Создать объект Gen2 для Strings.
Gen2 str0b2 = new G e n 2 (Обобщенный тест e t o b (В этой программе класс
Gen2 расширяет классно делает это с использованием специфичной строковой версии класса
Gen, как показывает следующее объявление Gen2 extends Gen {

Глава 14. Обобщения
3 8 Более того, внутри класса
Gen2 метод getob
() переопределен с типом возвращаемого значения Ч Ориентированная на строки перегрузка getob().

String g e t o b () Вызван String getob(): ");
return Все это совершенно допустимо. Единственная проблема в том, что из-за очистки типов ожидаемая форма метода getob
() будет выглядеть так g e t o b () { // Чтобы справиться с этой проблемой, компилятор создает метод-мост с показанной выше сигнатурой, который вызывает строковую версию. То есть, если вы посмотрите на интерфейс класса
Gen2 с помощью javap, то увидите следующие методы Gen2 extends Gencjava.lang.String> {

Gen2(java.lang.String);
j a v a .lang.String g e t o b ();
java. lang. Object g e t o b O ; // метод-мост
}
Как видите, сюда включен метод-мост. (Комментарий добавлен автором, а не j avap, а вывод, который вы видите, может измениться в зависимости от используемой версии языка Последнее замечание о методах-мостах. Обратите внимание на то, что единственная разница между двумя методами getob
() заключается в типе возвращаемого значения. Обычно это вызывает ошибку, но поскольку происходит не вис ходном коде, проблема не возникает и JVM успешно справляется с этой ситуацией.
Ошибки неоднозначности
Включение в язык обобщений породило новый тип ошибок, от которых вам нужно защищаться, — неоднозначность (ambiquity). Ошибки неоднозначности случаются, когда очистка порождает два внешне разных обобщенных объявления, разрешаемых в виде одного очищенного типа, что вызывает конфликт. Вот пример, который включает перегрузку методов Неоднозначность порождается очисткой перегруженных методов
class MyGenClass
V> Т obi;
V ob 2 ;
/ / . .
// Эти два перегруженных метода неоднозначны
// и не компилируются
void s e t (То) {
obi = о s e t(Vo) {
ob2 = о ;
}
}

3 8 Часть I. Язык Обратите внимание на то, что класс M y G en C lass объявляет два обобщенных типа Т и V. Внутри класса M y G en C lass предпринимается попытка перегрузить метод s e t () на основе параметров Т и V. Это выглядит вполне резонно, потому что кажется, что параметры типа Т и V содержат разные типы . Однако здесь возникают две проблемы н еоднозначности.
П ервая — судя потому, как написан класс M y G en C lass, нет требования, чтобы параметры Т и V содержали разные типы . Например, объект M y G en C lass, в принципе, вполне можно создать следующим образом String> obj = new MyGenClass
В этом случае параметры типа Т и V будут заменены типом S t r i n g . Это делает обе версии метода s e t () идентичными, что, конечно же, представляет собой ошибку.
В торая, более фундаментальная проблема состоит в том, что очистка типов приводит обе версии метода к следующему виду set(Object о) { // То есть перегрузка метода s e t ( ) , которую пытается осуществить класс
M y G en C lass, в действительности неоднозначна.
О шибки неоднозначности иногда трудно исправить. Например, если вызнаете, что параметр типа всегда будет получать некий подтип класса S t r i n g , том ож ете попытаться исправить класс M y G en C lass, переписав его объявление следующим образом MyGenClass
V extends String> { // почти Это позволит скомпилировать класс M y G en C lass, ивы даже сможете создавать экземпляры его объектов примерно так String> х = new MyGenClass
Это работает, потому что Java может аккуратно определить, когда какой метод должен быть вызван. Однако неоднозначность возникнет, когда вып опы таетесь выполнить следующую строку String> х = new MyGenClass
Поскольку в этом случае параметры типа Т и V получают тип, то какую версию метода s e t () нужно вызвать Вызов метода s e t () теперь неоднозначен.
Ч естн о говоря, в предыдущем примере было бы лучше использовать два метода с разными именами, вместо того чтобы перегружать метод s e t ( ) . Разрешение неоднозначности зачастую требует реструктуризации кода, потому что неоднозначность свидетельствует о концептуальной ошибке в дизайне.
Некоторые ограничения обобщений
Существует несколько ограничений, о которых следует помнить, применяя обобщения. Они включают создание объектов типа параметров, статических членов, исключений и массивов. Каждое из них мы рассмотрим далее.
Нельзя создавать экземпляр типа параметра
С оздать экземпляр типа параметра невозможно. Например, рассмотрим такой класс
Глава 14. Обобщения
3 8 9
// Нельзя создавать экземпляр типа Т
class Gen Т ob;
G e n () {
ob = new T ();
I I
Недопустимо Здесь осуществляется недопустимая попытка создать экземпляр типа Т. Причину понять просто поскольку тип Т не существует вовремя выполнения, как компилятор может узнать, какого типа объект следует создать Вспомните, что очистка удаляет все параметры типа в процессе компиляции.
Ограничения на статические члены
Никакой статический член не может использовать тип параметра, объявленный в его классе. Например, оба статических члена этого класса являются недопустимыми Wrong {

// Неверно, нельзя создать статические переменные типа Т
static Т ob;
// Неверно, ни один статический метод не может использовать Т
static Т g e t o b () {
return Несмотря на то что вы не можете объявить статические члены, которые используют тип параметра, объявленный в окружающем классе, вы можете объявлять обобщенные статические методы, определяющие их собственные параметры типа, как это делалось ранее в настоящей главе.
Ограничения обобщенных массивов
Существует два важных ограничения обобщений, касающиеся массивов. Во- первых, вы не можете создать экземпляр массива, тип элемента которого — параметр типа. Во-вторых, вы не можете создать массив специфичных для типа обобщенных ссылок. В следующей короткой программе демонстрируются обе ситуации Обобщения и массивы
class GencT extends Number> {
T ob;
T v a l s []; // OK
Gen(T о, T [] nums) {
ob = o;
// Этот оператор неверен vals = new T[10];
// нельзя создавать массив объектов Т
// Однако этот оператор верен = nums; // можно присвоить ссылку существующему массиву

3 9 Часть I. Язык Java
class GenArrays {
public static void main(String a r g s []) {
Integer n[] = {
1 , 2 ,
3, 4, 5 };
Gen iob = new Gen(50, n ) ?
// Нельзя создать массив специфичных для типа обобщенных ссылок Gen g e n s [] = new Gen[10]; // Неверно Это верно g e n s [] = new Gen[10]; // Как показывает эта программа, объявлять ссылку на массив объектов типа Т допустимо, как это сделано в следующей строке.
Т v a l s []; / / O Тем не менее нельзя создать массив объектов типа Т, как показано в следующей закомментированной строке vals = new Т [10]; // нельзя создавать массив объектов Т
Причина, по которой нельзя создать массив объектов типа Т, связана стем, что тип Т не существует вовремя выполнения, а потому у компилятора нет способа узнать, массив элементов какого типа в действительности надо создавать.
Однако вы можете передать ссылку на совместимый по типу массив конструктору
Gen ()
, когда объект создается, и присвоить эту ссылку vals, как это делается в представленной ниже строке программы = nums; // можно присвоить ссылку существующему массиву

Это работает, поскольку массив, переданный классу
Gen, имеет известный тип, который будет тем же типом, что и тип Т на момент создания объекта.
Обратите внимание на то, что внутри метода main
() вы не можете объявить массив ссылок на объекты специфического обобщенного типа. То есть строка Gen g e n s [] = new Gen[10]; // Неверно!

не компилируется. Массивы специфических обобщенных типов, попросту, недопустимы, так как они могут нарушить безопасность типов.
Вы можете создать массив ссылок на обобщенный тип, если используете шаблоны gens[] = new Gen[10];
11
Этот подход лучше применения массивов базовых типов, поскольку, по крайней мере, некоторые проверки типа по-прежнему могут быть выполнены компи­
лятором.
Ограничения обобщенных исключений Обобщенный класс не может расширять класс
Throwable. Это значит, что вы не сможете создать обобщенные классы исключений
ЧАСТЬ
1   ...   25   26   27   28   29   30   31   32   ...   90


написать администратору сайта