Философия Java3
Шрифт:
II: generics/ArrayOfGeneriс.java
public class ArrayOfGeneriс {
static final int SIZE = 100, static Generic<Integer>[] gia; @SuppressWarni ngs("unchecked") public static void main(String[] args) {
// Компилируется, но приводит к ClassCastException:
//! gia = (Generic<Integer>[])new Object[SIZE];
II Тип времени выполнения является "стертым" type:
gia = (Generic<Integer>[])new Generic[SIZE];
System.out.pri ntin(gi a.getClass.getSi mpleName);
gia[0] = new Generic<Integer>;
//! gia[l] = new ObjectO; II Ошибка компиляции
II
//! gia[2] = new Generic<Doublе>;
}
} /* Output:
Generic[]
*///:-
Проблема в том, что массивы отслеживают свой фактический тип, который задается в точке создания массива. Таким образом, даже несмотря на то, что gia преобразуется в Generic<Integer>[], эта информация существует только на стадии компиляции (а без директивы @SuppressWarnings вы получите предупреждение). Во время выполнения мы по-прежнему имеем дело с массивом Object, и это создает проблемы. Успешно создать массив параметризованного типа можно только одним способом — создать новый массив «стертого» типа и выполнить преобразование.
Рассмотрим чуть более сложный пример. Допустим, имеется простая параметризованная «обертка» для массива:
//: generics/GenericArray java
public class GenericArray<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArray(int sz) {
array = (T[])new Object[sz];
}
public void put(int index, T item) { array[index] = item;
}
public T get(int index) { return array[index]; } // Метод, предоставляющий доступ к базовому представлению: public T[] rep { return array; } public static void main(String[] args) { GenericArray<Integer> gai =
new GenericArray<Integer>(10); // Приводит к ClassCastException: //! Integer[] ia = gai.rep: // А так можно. Object[] oa = gai.rep;
}
} ///:-
Как и прежде, мы не можем использовать запись Т[] array = new T[sz], поэтому мы создаем массив объектов и преобразуем его.
Метод гер возвращает Т[]; в методе main для gai это должен быть тип Integerf], но при попытке вызова и сохранения результата по ссылке на Integer[] будет получено исключение ClassCastException — это снова происходит из-за того, что фактическим типом объекта времени выполнения является Object[]. Если мы немедленно проводим преобразование к Т[], то на стадии компиляции фактический тип массива теряется и компилятор может упустить некоторые потенциальные ошибки. Из-за этого лучше использовать в коллекции Object[], а затем добавить преобразование к Т при использовании элемента массива. Вот как это будет выглядеть в примере GenericArray.java:
//: generics/GenericArray2.java
public class GenericArray2<T> { private Object[] array; public GenericArray2(int sz) { array = new Object[sz];
}
public void put(int index, T item) { array[index] = item;
}
@SuppressWarnings("unchecked")
public T get(int index) { return (T)array[index]; }
@SuppressWarnings("unchecked")
public T[] rep О { продолжение &
return (T[])array; // Предупреждение: непроверенное преобразование
}
public static void main(String[] args) { GenericArray2<Integer> gai =
new GenericArray2<Integer>(10); for(int i = 0: i < 10: i ++)
gai.put(i, i): for(int i = 0: i < 10; i ++)
System.out.print(gai.get(i) + " "); System.out.printlnO; try {
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,
На
В новом коде следует передавать метку типа. В обновленной версии 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 подобные преобразования встречаются неоднократно. И конечно, при их компиляции выдается множество предупреждений.