Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 86 87 88 89 90 91 92 93 94 ... 132

Integer[] ia = gai.rep(); } catch(Exception e) { System.out.printin(e); }

}

} /* Output: (Sample)

0 12 3 4 5 6 7 8 9

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer,

На первый взгляд почти ничего не изменилось, разве что преобразование типа было перемещено. Без директив @SuppressWarnings вы по-прежнему будете получать предупреждения, но теперь во внутренней реализации используется Object[] вместо Т[]. При вызове get() объект преобразуется к Т; это правильный тип, поэтому преобразование безопасно. Но при вызове гер() снова делается попытка преобразования Object[] в Т[], которое остается неверным; в результате вы получите предупреждение во время компиляции и исключение во время выполнения. Не существует способа обойти тип базового массива, которым может быть только Object[]. У внутренней интерпретации array как Object[] вместо Т[] есть свои преимущества: например, вы с меньшей вероятностью забудете тип массива, что приведет к случайному появлению ошибок (впрочем, подавляющее большинство таких ошибок будет быстро выявлено на стадии выполнения).

В новом коде следует передавать метку типа. В обновленной версии Generic-Array выглядит так:

//: generics/Generic/rrayWithTypeToken.java

import java.lang.reflect.*;

public class GenericArrayWithTypeToken<T> { private T[] array; @SuppressWarni ngs("unchecked")

public GenericArrayWithTypeToken(CIass<T> type, int sz) { array = (T[])Array.newInstance(type, sz);

}

public void put(int index, T item) { arrayCindex] = item;

}

public T get(int index) { return arrayCindex]; } // Expose the underlying representation: public T[] rep() { return array; } public static void main(String[] args) {

GenericArrayWithTypeToken<Integer> gai =

new Generi cArrayWi thTypeToken<Integer>( Integer.class, 10);

// This now works:

Integer[] ia = gai.rep();

}

} ///

Метка типа Class<T> передается конструктору для восстановления информации после стирания, чтобы мы могли создать фактический тип нужного массива (предупреждения при преобразовании по-прежнему приходится подавлять @SuppressWarnings). Получив фактический тип, мы возвращаем его для получения желаемых результатов, как видно из main().

К сожалению, просмотрев исходный код стандартных библиотек Java SE5, вы увидите, что преобразования массивов Object в параметризованные типы происходят повсеместно. Например, вот как выглядит копирующий конструктор для создания ArrayList из Collection после некоторой правки и упрощения:

public ArrayList(Collection с) { size = c.sizeO;

elementData = (E[])new Object[size]; с.toArray(elementData):

}

В ArrayList.java подобные преобразования встречаются неоднократно. И конечно, при их компиляции выдается множество предупреждений.

Ограничения

Ограничения, уже упоминавшиеся ранее в этой главе, сужают круг параметров типов, используемых при параметризации. Хотя это позволяет предъявлять требования к типам, к которым применяется ваш параметризованный код, у ограничений имеется и другой, потенциально более важный эффект: возможность вызова методов, определенных в ограничивающих типах.

Поскольку стирание уничтожает информацию о типе, при отсутствии ограничений для параметров типов могут вызываться только методы Object. Но, если ограничить параметр подмножеством типов, вы сможете вызвать методы из этого подмножества. Для установления ограничений в Java используется ключевое слово extends. Важно понимать, что в контексте параметризации extends имеет совершенно иной смысл, нежели в обычной ситуации. Следующий пример демонстрирует основы установления ограничений:

//: generics/BasicBounds.java

interface HasColor { java. awt. Col or getColorO; }

class Colored<T extends HasColor> { T item:

Colored(T item) { this.item = item; }

T getltemO { return item; }

// Ограничение позволяет вызвать метод:

java. awt. Col or colore) { return item.getColorO; }

}

class Dimension { public int x, y. z; }

// Не работает -- сначала класс, потом интерфейсы: // class ColoredDimensiол<Т extends HasColor & Dimension> {

// Несколько ограничений-

class ColoredDimension<T extends Dimension & HasColor> { T item:

ColoredDimension(T item) { this.item = item, }

T getltemO { return item, }

java. awt. Col or colorO { return item getColorO; }

int getXO { return item.x; }

int getYO { return item.у, }

int getZO { return item z; }

}

interface Weight { int weightO; }

// Как и при наследовании, конкретный класс может быть только один, // а интерфейсов может быть несколько: class Solid<T extends Dimension & HasColor & Weight> { T item;

Solid(T item) { this.item = item. }

T get ItemО { return item; }

java.awt Color col orО { return item.getColor(); }

int getXO { return item x; }

int getYO { return item у; }

int getZO { return item.z; }

int weightO { return item, weight О; }

}

class Bounded

extends Dimension implements HasColor. Weight {

public java.awt.Col or getColorO { return null; } public int weightO { return 0; }

}

public class BasicBounds {

public static void main(String[] args) { Solid<Bounded> solid =

new Solid<Bounded>(new BoundedO); solid.colorO; solid.getYO. solid.weightO;

}

} ///

Вероятно, вы заметили, что пример BasicBounds.java содержат некоторую избыточность, которая может быть устранена посредством наследования. С каждым уровнем наследования добавляются новые ограничения:

II: generics/InheritBounds.java

class HoldItem<T> { T item;

HoldItem(T item) { this.item = item; } T getltemO { return item; }

}

class Colored2<T extends HasColor> extends HoldItem<T> {

Colored2(T item) { super(item). }

java awt Color col or О { return item.getColorO; }

}

class ColoredDimension2<T extends Dimension & HasColor> extends Colored2<T> {

ColoredDimension2(T item) { super(item); } int getXO { return item x; } int getYO { return item.y, } int getZO { return item z; }

}

class Solid2<T extends Dimension & HasColor & Weight> extends ColoredDimension2<T> {

Solid2(T item) { super(item); } int weight О { return item.weightO; }

}

public class InheritBounds {

public static void main(String[] args) { Solid2<Bounded> solid2 =

new Solid2<Bounded>(new BoundedO); solid2 colorO: solid2 getYO; solid2 weightO,

}

} ///.-

Holdltem просто хранит объект; это поведение наследуется классом Colored2, который также требует, чтобы его параметр реализовывал HasColor. ColoredDi-mension2 и Solid2 продолжают расширение иерархии и добавляют на каждом уровне новые ограничения. Теперь методы наследуются, и их не нужно повторять в каждом классе.

Пример с большим количеством уровней:

//: generics/EpicBattlе.java // Demonstrating bounds in Java generics, import java util.*;

interface Superpower {} interface XRayVision extends Superpower { void seeThroughWallsO;

}

interface SuperHearing extends Superpower { void hearSubtleNoisesO;

}

interface SuperSmell extends Superpower { void trackBySmellO;

}

class SuperHero<POWER extends SuperPower> { POWER power;

SuperHero(POWER power) { this.power = power; } POWER getPower0 { return power; }

}

class SuperSleuth<POWER extends XRayVision>

extends SuperHero<POWER> {

SuperSleuth(POWER power) { super(power); } void see() { power. seeThroughWallsO: }

}

class CanineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER> {

CanineHero(POWER power) { super(power); } void hearO { power.hearSubtleNoisesO; } void smell О { power.trackBySmell0; }

}

class SuperHearSmell implements SuperHearing, SuperSmell { public void hearSubtleNoisesO {} public void trackBySmell0 {}

}

class DogBoy extends CanineHero<SuperHearSmell> { DogBoyO { super(new SuperHearSmell0); }

}

public class EpicBattle {

// Ограничения в параметризованных методах: static <POWER extends SuperHearing> void useSuperHearing(SuperHero<POWER> hero) { hero, get Power () hearSubtleNoisesO:

}

static <POWER extends SuperHearing & SuperSmell> void superFind(SuperHero<POWER> hero) {

hero, get Power 0 .hearSubtleNoisesO; hero.getPower() .trackBySmel 10;

}

public static void main(String[] args) { DogBoy dogBoy = new DogBoyO; useSuperHearing(dogBoy); superFind(dogBoy); // Так можно:

List<? extends SuperHearing> audioBoys; // А так нельзя:

// List<? extends SuperHearing & SuperSmell> dogBoys;

}

} ///:-

Метасимволы

Мы уже встречали простые примеры использования метасимволов — вопросительных знаков в выражениях аргументов параметризации — в главах И и 13. В этом разделе тема будет рассмотрена более подробно.

Начнем с примера, демонстрирующего одну особенность массивов: массив производного типа можно присвоить ссылке на массив базового типа:

II: generics/CovariantArrays.java class Fruit {}

class Apple extends Fruit {}

class Jonathan extends Apple {} class Orange extends Fruit {}

public class CovariantArrays {

public static void main(String[] args) { Fruit[] fruit = new Apple[10]; fruit[0] = new AppleO; // OK fruit[l] = new JonathanO; // OK

// Тип времени выполнения - Applet], а не Fruit[] или Orange[]: try {

// Компилятор позволяет добавлять объекты Fruit: fruit[0] = new FruitO; // ArrayStoreException } catch(Exception e) { System.out.println(e): } try {

// Компилятор позволяет добавлять объекты Orange: fruit[0] = new OrangeO; // ArrayStoreException } catch(Exception e) { System.out.println(e); }

}

} /* Output:

java.1ang.ArrayStoreException: Fruit java.1ang.ArrayStoreExcepti on: Orange *///:-

Первая строка main() создает массив Apple и присваивает его ссылке на массив Fruit. Выглядит логично — Apple является разновидностью Fruit, поэтому массив Apple также одновременно должен быть массивом Fruit.

С другой стороны, если фактическим типом массива является Арр1е[], в массиве можно разместить только Apple или субтип Apple, причем это правило должно соблюдаться как во время компиляции, так и во время выполнения. Но обратите внимание на то, что компилятор также позволит разместить в массиве ссылку на объект Fruit. Для компилятора это вполне логично, потому что он имеет дело со ссылкой Fruit[] — так почему бы не разрешить занести в массив объект Fruit или любого типа, производного от Fruit, — скажем, Orange? Во время компиляции это разрешено. Однако механизм времени выполнения знает, что он имеет дело с Apple [], и при попытке занесения постороннего типа происходит исключение.

Впрочем, для массивов это не создает особых проблем, потому что при вставке объекта неверного типа вы об этом очень быстро узнаете во время выполнения. Но одна из основных целей параметризации как раз и состоит в том, чтобы по возможности переместить выявление подобных ошибок на стадию выполнения. Итак, что же произойдет при использовании параметризованных контейнеров вместо массивов?

//: generics/NonCovariantGenerics.java // {CompileTimeError} (Won't compile) import java.util.*:

public class NonCovariantGenerics {

1 ... 86 87 88 89 90 91 92 93 94 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий