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

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


Скачать 25.04 Mb.
НазваниеС. Н. Тригуб Перевод с английского и редакция
АнкорJava. Полное руководство. 8-е издание.pdf
Дата28.02.2017
Размер25.04 Mb.
Формат файлаpdf
Имя файлаJava. Полное руководство. 8-е издание.pdf
ТипДокументы
#3236
страница27 из 90
1   ...   23   24   25   26   27   28   29   30   ...   90
ГЛАВА
Обобщения
После выхода в 1995 году первоначальной версии 1.0 в язык Java было добавлено множество новых средств. Одним из наиболее значительных и влиятельных новшеств стали обобщения (generic). Во-первых, их появление означало добавление новых синтаксических элементов в язык. Во-вторых, они повлекли за собой изменения во многих классах и методах самого ядра API. Сегодня обобщения — это неотъемлемая часть программирования на языке Java, и твердое понимание этого важного средства обязательно. Здесь оно исследуется подробно.
Применение обобщений позволило создавать классы, интерфейсы и методы, работающие безопасным к типам способом с разнообразными видами данных. Многие алгоритмы логически идентичны, независимо оттого, к данным каких типов они применяются. Например, механизм, поддерживающий стеки, является одними тем же в стеках, хранящих элементы классов
Integer, String, Object или
Thread. Благодаря обобщениям, вы можете определить алгоритм однажды, независимо от конкретного типа данных, а затем применять его к широкому разнообразию типов данных без каких-либо дополнительных усилий. Впечатляющая мощь добавленных к языку обобщений фундаментально изменила способы написания кода Вероятно, одно из средств Java, которое в наибольшей степени испытало влияние обобщ ений,—это инфраструктура коллекций Collections Framework. Упомянутая инфраструктура является частью API Java и подробно описана в главе 17, но стоит вкратце пояснить ее здесь. Коллекция — это группа объектов. Инфраструктура коллекций определяет несколько классов, таких как списки и карты, которые управляют коллекциями. Классы коллекций всегда готовы работать с объектами любых типов. Выгода от добавления в язык обобщений состоит в том, что классы коллекций теперь могут использоваться с полным обеспечением безопасности типов. То есть, помимо предоставления мощного элемента языка, обобщения также значительно усовершенствовали существующие средства. Вот почему обобщения представляют собой столь значимое дополнение к языку В настоящей главе описаны синтаксис, теория и применение обобщений. Мы покажем, как обобщения обеспечивают безопасность типов в некоторых ранее трудных случаях. Когда вы изучите эту главу, то захотите ознакомиться с главой 17, описывающей систему коллекций. Там вы найдете множество примеров работы обобщений.
Помните! Обобщения были добавлены в J2SE 5.0. Использующий их код не может быть скомпилирован старыми версиями Что такое обобщения
По сути дела, обобщения — это параметризованные типы Эти типы важны, поскольку позволяют объявлять классы, интерфейсы и методы, где тип данных, ко

3 5 Часть I. Язык Java
торыми они оперируют, указан в виде параметра. Используя обобщения, можно создать единственный класс, который, например, будет автоматически работать с разными типами данных. Классы, интерфейсы или методы, имеющие дело с параметризованными типами, называются обобщениями, обобщенными классами или обобщенными методами.
Важно понимать, что язык Java всегда предлагал возможность создавать в определенной мере обобщенные классы, интерфейсы и методы, оперирующие ссылками на тип
Obj ect. Поскольку тип
Ob j ect
— это суперкласс для всех остальных классов, ссылка на тип
Obj ect может обращаться к объекту любого типа. То есть в старом коде обобщенные классы, интерфейсы и методы использовали ссылки на тип
Obj ect для того, чтобы оперировать объектами различного типа. Проблема была в том, что они не могли обеспечить безопасность типов.
Обобщения добавили в язык безопасность типов, которой так не хватало. Они также упростили процесс выполнения, поскольку теперь нет необходимости применять явные приведения для транслирования объектов класса
Object в реальные типы данных, с которыми выполняются действия. Благодаря обобщениям, все приведения выполняются автоматически и неявно. То есть обобщения расширили ваши возможности повторного использования кода и позволили вам делать это легко и безопасно.
На заметку Предупреждение для программистов C++: хотя обобщения похожи на шаблоны в C++, это не одно и тоже. Существует ряд фундаментальных отличий между двумя подходами к обобщенным типам. Если у вас имеется опыт применения языка C++, важно не делать поспешных выводов о том, как обобщения работают в языке Простой пример обобщения
Давайте начнем с простого примера обобщенного класса. В следующей программе определены два класса. Первый — это обобщенный класса второй —
GenDemo, класс использующий класс
Gen.
// Простой обобщенный класс Здесь Т — это параметр типа который будет заменен реальным типом
// при создании объекта класса Gen.
class Gen Т ob; // объявление объекта типа Т Передать конструктору ссылку
// на объект типа Т e n То о ;
}
// Вернуть Т g e t o b O {
return ob;
}
// Показать тип Т
void showType() Типом T является " + o b .getClass().getName());
}
}
Глава 14. Обобщения
3 5 3
// Демонстрация обобщенного класса GenDemo {
public static void main(String a r g s []) {
// Создать ссылку для Integers.
Gen iOb;
// Создать объект Gen и присвоить
// ссылку на iOb. Отметьте применение автоупаковки
// для инкапсуляции значения 88 в объект Integer.
iOb = new Gen(88);
// Показать тип данных, используемый iOb.
iO b .showType();
// Получить значение iOb. Обратите внимание что никакого приведения ненужно значение " + v ) ;
System.out.println();
// Создать объект Gen для String.
Gen strOb = new Gen (Обобщенный тест Показать тип данных, используемый strOb.
strOb.showType();
// Получить значение strOb. Опять же
// приведение не требуется str = Значение " + Результат работы этой программы.
Типом Т является java.lang.Integer Значение Типом Т является java.lang.String
Значение Обобщенный тест

Давайте внимательно исследуем эту программу. Обратите внимание на объявление класса
Gen в следующей строке Gen Здесь Т
— имя параметра типа Это имя используется в качестве заполнителя, куда будет подставлено имя реального типа, переданного классу
Gen при создании реальных типов. То есть параметр типа Т применяется в классе
Gen всякий раз, когда требуется параметр типа. Обратите внимание на то, что тип Т заключен в угловые скобки о . Этот синтаксис может быть обобщен. Всякий раз, когда объявляется параметр типа, он указывается в угловых скобках. Поскольку класс
Gen применяет параметр типа, класс
Gen является обобщенным классом, который называется также параметризованным типом.

Далее тип Т используется для объявления объекта по имени ob, как показано ниже.
Т ob; // объявляет объект типа Т
Как упоминалось, Т
— это место для подстановки реального типа, который будет указан при создании объекта класса
Gen. То есть объект ob будет объектом Зак. 3030

3 5 Часть I. Язык типа, переданного в параметре типа Т. Например, если в параметре Т передан тип
String, то экземпляр ob будет иметь тип Теперь рассмотрим конструктор
Gen ().
G e n То о;
}
Как видите, параметр о имеет тип Т. Это значит, что реальный тип параметра о определяется типом, переданным параметром типа Т при создании объекта класса
Gen. К тому же, поскольку и параметр о, и переменная-член ob имеют тип Тони оба получают одинаковый реальный тип при создании объекта класса Параметр типа Т также может быть использован для указания типа возвращаемого значения метода, как в случае метода getob ()
, показанного здесь.
Т g e t o b () {
return Так как объект ob тоже имеет тип Т, его тип совместим с типом, возвращаемым методом getob (Метод showType
() отображает тип Т вызовом метода getName
() объекта класса
Class, возвращенным вызовом метода getClass
() объекта ob. Метод getClass
() определен в классе
Object и потому является членом всех классов. Он возвращает объект класса
Class, соответствующий типу класса объекта, для которого он вызван. Класс
Class определяет метод getName ()
, который возвращает строковое представление имени класса.
Класс
GenDemo демонстрирует обобщенный класс
Gen. Сначала он создает версию класса
Gen для целых чисел, как показано ниже Посмотрим на это объявление внимательней. Отметим, что тип
Integer указан в угловых скобках после слова
Gen. В этом случае
Integer
— это аргумент типа, который передается в параметре типа Т класса
Gen. Это фактически создает версию класса
Gen, в которой все ссылки на тип Т транслируются в ссылки на тип
Integer. То есть в данном объявлении объект ob имеет тип
Integer, и тип возвращаемого значения метода getob
() также имеет тип Прежде чем двигаться дальше, необходимо заявить, что компилятор Java на самом деле не создает различные версии класса
Gen или любого другого обобщенного класса. Хотя было бы удобно считать так, на самом деле подобное не происходит. Вместо этого компилятор удаляет всю обобщенную информацию о типах, выполняя необходимые приведения, чтобы сделать поведение вашего кода таким, будто создана специфическая версия класса
Gen. То есть имеется только одна версия класса
Gen, которая существует в вашей программе. Процесс удаления обобщенной информации о типе называется очисткой (erasure), и мы вернемся к этой теме чуть позднее в настоящей главе.
Следующая строка присваивает объекту iOb ссылку на экземпляр целочисленной версии класса
Gen.
iOb = new Отметим, что когда вызывается конструктор
Gen ()
, аргументтипа
Integer также указывается. Это необходимо, потому что типом объекта (в данном случае объекта iOb), которому присваивается ссылка, является тип
Gene
Int eger>. То есть ссылка, возвращаемая оператором new, также должна иметь тип
Gene
Int eger>.
Глава 14. Обобщения
3 5 Если это не так, получается ошибка времени компиляции. Например, следующее присваивание вызовет ошибку компиляции = new О ); // Ошибка!
Поскольку объект iOb имеет тип
Gene
Int eger>, он не может быть использован для присваивания ссылки типа
Gen. Эта проверка типа является одним из основных преимуществ обобщений, потому что обеспечивает безопасность типов.
Как указано в комментарии к программе, присваивание = new использует автоупаковку для инкапсуляции значения 88, имеющего тип int, в объекте класса
Integer. Это работает, потому что тип
Gene
Int eger> создает конструктор, принимающий аргумент класса
Integer. Поскольку ожидается объект класса
Integer,
Java автоматически упаковывает 88 внутрь него. Конечно, присваивание также может быть написано явно, как здесь = new Gen(new Однако с этой версией не связано никаких преимуществ.
Программа затем отображает тип объекта ob внутри объекта iOb, которым является
Integer. Далее программа получает значение объекта ob в следующей строке v = Поскольку возвращаемым типом метода getob
() будет Т, который заменяется на
Integer при объявлении объекта iOb, то возвращаемым типом метода getob
() также будет класс
Integer, который автоматически распаковывается в тип int и присваивается переменной v, имеющей тип int. То есть нет никакой необходимости приводить тип возвращаемого значения метода getob
() к классу
Integer. Конечно, использовать автоупаковку необязательно. Предыдущая строка может быть написана так v = Однако автоупаковка позволяет сделать код более компактным.
Далее в классе
GenDemo объявляется объект типа
Gen.
Gen strOb = new Gen(" Обобщенный тест");
Поскольку аргументом типа является
String, класс
String подставляется вместо параметра Т внутри класса
Gen. Это создает (концептуально) строковую версию класса
Gen, что и демонстрируют остальные строки программы.
Обобщения работают только с объектами
Когда объявляется экземпляр обобщенного типа, аргумент, переданный в качестве параметра типа, должен быть типом класса. Вы не можете использовать элементарный тип вроде int или char. Например, классу
Gen можно передать в параметре Т любой тип класса, но нельзя передать элементарный тип. Таким образом, следующее объявление недопустимо intOb = new Gen(53); // Ошибка, нельзя использовать элементарные типы

3 5 Часть I. Язык Конечно, невозможность использовать элементарный тип не является серьезным ограничением, так как вы можете применять оболочки типов (как это и делается в предыдущем примере) для инкапсуляции элементарных типов. Более того, механизм автоупаковки и автораспаковки Java делает использование оболочек типов прозрачным.
Отличие обобщенных типов в зависимости от аргументов типа
Ключевой момент в понимании обобщенных типов в том, что ссылка на одну специфическую версию обобщенного типа несовместима с другой версией того же обобщенного типа. Например, следующая строка, если ее добавить к предыдущей программе, вызовет ошибку и программа не будет откомпилирована = strOb; / / Не верно!

Даже несмотря на то, что объекты iOb и strOb имеют тип
Gen, они являются ссылками на разные типы, потому что типы их параметров отличаются. Это часть того способа, благодаря которому обобщения обеспечивают безопасность типов и предотвращают ошибки.
Обобщения повышают безопасность типов
Теперь вы можете задать себе следующий вопрос если те же функциональные возможности, которые мы обнаружили в обобщенном классе
Gen, могут быть получены без обобщений, те. простым указанием класса
Object в качестве типа данных и применением правильных приведений, тов чем же выгода оттого, что класс
Gen параметризован Ответ в том, что обобщения автоматически гарантируют безопасность типов во всех операциях, где задействован класс
Gen. В процессе работы с ним исключается необходимость явного приведения и ручной проверки типов в коде.
Чтобы понять выгоды от обобщений, для начала рассмотрим следующую программу, которая создает необобщенный эквивалент класса
Gen.
// NonGen — функциональный эквивалент Gen,
// не использующий обобщений
class NonGen {
Object ob; // ob теперь имеет тип Object
// Передать конструктору ссылку на объект типа Object
NonGen(Object о) {
ob = о ;
}
// Вернуть тип Object.
Object g e t o b () {
return ob;
}
// Показать тип ob.
void showType() Типом ob является " +
o b .getClass().getName());
}
}
Глава 14. Обобщения
3 5 7
// Демонстрация необобщенного класса
class NonGenDemo {
public static void main(String a r g s []) {
NonGen iOb;
// Создать объект NonGen и сохранить
// Integer в нем. Автоупаковка используется
iOb = new N o n G e n (88);
// Показать тип данных, используемый iOb.
i O b .showType();
// Получить значение iOb.
// На этот раз приведение необходимо
int v = (Integer) значение " + v ) ;
System.out.println();
// Создать другой объект NonGen и
// сохранить в нем String.
NonGen strOb = new N o n G e n (Тест без обобщений Показать тип данных, используемый strOb.
strOb.showType();
// Получить значение strOb.
// Опять же — приведение необходимо str = (String) strOb.g e t o b (Значение " + str);
// Это компилируется, но концептуально неверно
iOb = strOb;
v = (Integer) iOb.getobO; // ошибка времени выполнения!
}
}
В этой версии программы присутствует несколько интересных моментов. Для начала класс
NonGen заменяет все обращения к типу Т на обращения к типу
Ob j ec t
. Это позволяет классу
NonGen хранить объекты любого типа, как это делает и обобщенная версия. Однако это не позволяет компилятору Java иметь какую- то реальную информацию о типе данных, в действительности сохраняемых в объекте класса
NonGen, что плохо по двум причинам. Во-первых, для извлечения сохраненных данных требуется явное приведение. Во-вторых, многие ошибки несоответствия типов не могут быть обнаружены до времени выполнения. Рассмотрим каждую из этих проблем поближе.
О братите внимание на эту строку v = (Integer) Поскольку возвращаемым типом метода getob
() является тип
Obj ect, необходимо привести его к типу
Integer, чтобы позволить выполнить автораспаков­
ку и сохранить значение в переменной v. Если убрать приведение, программа не компилируется. В версии с обобщением приведение происходит неявно. В версии без обобщения приведение должно быть явным. Это не только приносит неудобство, но и является потенциальным источником ошибок.
Теперь рассмотрим следующую кодовую последовательность в конце программы

3 5 Часть I. Язык Java
// Это компилируется, но концептуально неверно
iOb = strOb;
v = (Integer) iOb.getob(); // ошибка времени выполнения!
Здесь объект strOb присваивается объекту iOb. Однако объект strOb ссылается на объект, содержащий строку, а нецелое число. Это присваивание синтаксически корректно, потому что все ссылки класса
NonGen одинаковы и любая ссылка класса
NonGen может указывать на любой другой объект типа
NonGen. Однако этот оператор семантически неверен, что и отражено в следующей строке. Здесь тип возвращаемого значения метода getob
() приводится к классу
Integer, аза тем делается попытка присвоить это значение переменной v. Проблема в том, что объект iOb теперь ссылается на объект, который хранит типа не
Integer. К несчастью, без использования обобщений компилятор Java не имеет возможности обнаружить это. Вместо этого передается исключение времени выполнения. Возможность создавать безопасный в отношении типов код, в котором ошибки несоответствия типов перехватываются компилятором, — это главное преимущество обобщений. Хотя использование ссылок на тип
Object для создания “псевдообобщенного” кода всегда возможно, нужно помнить, что такой код не является безопасным в отношении типов, и злоупотребление им приводит кис ключениям времени выполнения. Обобщения предотвращают подобные вещи. По сути, благодаря обобщениям, ошибки времени выполнения преобразуются в ошибки времени компиляции. Это и есть главное преимущество.
1   ...   23   24   25   26   27   28   29   30   ...   90


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