// Неоднозначность, вызванная стиранием перегруженных методов,class MyGenClass { Т obi; V ob2;// ...// Два следующих метода конфликтуют друг с другом,// поэтому код не компилируется.// Эти методы существенно неоднозначны.void set(T о) { obi = о;}void set(V о) { ob2 = о;}
}В классе MyGenClass объявлены два обобщенных типа: Т и V. В этом классе предпринимается попытка перегрузить метод set (). Перегружаемые методы отличаются параметрами типа т и V. Казалось бы, это не должно приводить к ошибке, поскольку типы Т и V отличаются. Но здесь возникают два затруднения в связи с неоднозначностью.Прежде всего, в классе MyGenClass не указано никаких требований, чтобы типы Т и V действительно отличались. В частности, не является принципиальной ошибкой создание объекта типа MyGenClass так, как показано ниже.
MyGenClass obj = new MyGenClass()В данном случае типы Т и V заменяются типом String. В результате оба варианта метода set () становятся совершенно одинаковыми, что, безусловно, считается ошибкой.Второе, более серьезное затруднение возникает в связи с тем, что при стирании обобщенных типов оба варианта метода set () преобразуются в следующий вид:
void set(Object о) { // ...Таким образом, попытка перегрузить метод set () в классе MyGenClass является существенно неоднозначной. В качестве выхода из этого затруднительного положения может стать отказ от перегрузки и использование двух разных имен методов.## Ограничения, накладываемые на обобщенияНа обобщения накладывается ряд ограничений, которые следует учитывать при их употреблении в программах на Java. К числу подобных ограничений относится создание объектов, определяемых параметром типа, использование статических членов класса, генерирование исключений и обращение с массивами. Рассмотрим все эти ограничения более подробно.### Невозможность получить экземпляры параметров типаЭкземпляр параметра типа получить невозможно. Рассмотрим в качестве примера следующий класс:
// Экземпляр типа Т получить нельзя,class Gen { Т ob; Gen () { ob = new T(); // Недопустимо!!! }}В данном примере любая попытка получить экземпляр типа т приводит к ошибке. Ее причину понять нетрудно: компилятору ничего не известно о типе создаваемого объекта, поскольку тип Т является заполнителем, который стирается во время компиляции.### Ограничения, накладываемые на статические члены классаВ статических членах нельзя использовать параметры типа, объявленные в содержащем их классе. Так, все объявления статических членов в приведенном ниже классе недопустимы.
class Wrong { // Статическую переменную типа Т создать нельзя, static Т ob;// В статическом методе нельзя использовать тип Т.static Т getob() { return ob;}// Статический метод не может обращаться к объекту типа Т.static void showob() { System.out.println(ob);}
}Несмотря на наличие описанного выше ограничения, допускается все же объявлять обобщенные статические методы, в которых используются собственные параметры типа. Примеры таких объявлений приводились ранее в этой главе.### Ограничения, накладываемые на обобщенные массивыНа массивы обобщенного типа накладываются два существенных ограничения. Во- первых, нельзя получить экземпляр массива, тип элементов которого определяется параметром типа. И во-вторых, нельзя создать массив обобщенных ссылок на объекты конкретного типа. Оба эти ограничения демонстрируются в приведенном ниже кратком примере программы.
// Обобщенные типы и массивы,class Gen { T ob;T vals[]; // Допустимо.Gen(T о, T[] nums) { ob = о; // Следующее выражение недопустимо: // vals = new Т[10]; // Нельзя создать массив типа Т. // Следующее выражение составлено верно. vals = nums; // Переменной допускается присваивать ссылку // на существующий массив.}
}
class GenArrays { public static void main(String args[]) { Integer n[] = { 1, 2, 3, 4, 5 }; Gen<Integer> iOb = new Gen<Integer>(50, n); // Нельзя создать массив обобщенных ссылок // на объекты конкретного типа. // Gen<Integer> gens[] = new Gen<Integer>[10]; // Ошибка! // Следующее выражение составлено верно. Gen<?> gens[] = new Gen<?>[10];}
}Как следует из исходного кода приведенной выше программы, допускается создавать ссылку на массив типа т. Это демонстрируется в следующей строке кода:
Т vals[]; // Допустимо.Но получить экземпляр самого массива типа т нельзя. Именно поэтому приведенная ниже строка кода закомментирована.
// vals = new Т[10]; // Нельзя создать массив типа Т.В данном случае ограничение на массив типа т состоит в том, что компилятору не известно, какого типа массив следует создавать на самом деле. Но в то же время конструктору Gen () можно передать ссылку на массив совместимого типа при создании объекта, а также присвоить это значение переменной vals. Примером тому может служить следующая строка кода:
vals = nums; // Переменной допускается присваивать ссылку // на существующий массив.Это выражение составлено верно, поскольку тип массива, передаваемого конструктору Gen () при создании объекта, известен и совпадает с типом Т. В теле метода main () содержится выражение, демонстрирующее невозможность объявить массив обобщенных ссылок на объекты конкретного типа. Поэтому приведенная ниже строка кода не будет скомпилирована.
// Gen gens[] = new Gen[10] ; // Ошибка!### Ограничения, накладываемые на обобщенные исключенияОбобщенный класс не может расширять класс Throwable. Это означает, что создавать обобщенные классы исключений нельзя.### Дальнейшее изучение обобщенийКак пояснялось в начале этой главы, приведенных в ней сведений достаточно для того, чтобы эффективно пользоваться обобщениями в создаваемых на Java программах. Но у обобщений имеется немало особенностей, которые не нашли отражения в этой главе.Читатели, которых заинтересовала эта тема, вероятно, захотят узнать побольше о томвлиянии, которое обобщения оказывают на иерархию классов и, в частности, каким образом осуществляется сравнение типов при выполнении программы, как переопределяются методы и т.д. Все эти и многие другие вопросы употребления обобщений подробно освещены в книге Java. Полное руководство, 8-е издание, ИД “Вильямс”, 2012 г.## Упражнение для самопроверки по материалу главы 131. Обобщения очень важны, поскольку они позволяют создавать код, который: a) обеспечивает типовую безопасность; b) пригоден для повторного использования; c) отличается высокой надежностью; d) обладает всеми перечисленными выше свойствами.2. Можно ли указывать простой тип в качестве аргумента типа?3. Как объявить класс FlightSched с двумя параметрами типа?4. Измените ваш ответ на вопрос 3 таким образом, чтобы второй параметр типа обозначал подкласс, производный от класса Thread.5. Внесите изменения в класс FlightSched таким образом, чтобы второй параметр типа стал подклассом первого параметра типа.6. Что обозначает знак ? в обобщениях?7. Может ли метасимвольный аргумент быть ограниченным?8. У обобщенного метода My Gen () имеется один параметр типа, определяющий тип передаваемого ему аргумента. Этот метод возвращает также объект, тип которого соответствует параметру типа. Как должен быть объявлен метод My Gen () ?9. Допустим, обобщенный интерфейс объявлен так, как показано ниже.interface IGenIF<T, V extends Т> { // ...```Составьте объявление класса MyClass, который реализует интерфейс iGenlF.
Допустим, имеется обобщенный класс Counter. Как создать объект его базового типа?
Существуют ли параметры типа на стадии выполнения программы?
Видоизмените ответ на вопрос 10 в упражнении по материалу главы 9 таким образом, чтобы сделать класс обобщенным. По ходу дела создайте интерфейс стека iGenStack, объявив в нем обобщенные методы push () и pop ().
Что обозначают угловые скобки (< >)?
Как упростить приведенную ниже строку кода в версии JDK 7?MyClass<Double,String> obj = new MyClass<Double,String>(1.1,"Hi");
Глава 14 Апплеты, события и прочее
Основные навыки и понятия
Представление об апплетах