Эккель Брюс
Шрифт:
На первый взгляд почти ничего не изменилось, разве что преобразование типа было перемещено. Без директив @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 {