Чтение онлайн

ЖАНРЫ

Философия Java3

Эккель Брюс

Шрифт:

//: generics/FactoryConstraint.java

interface FactoryI<T> { T createO;

}

class Foo2<T> { private T x:

public <F extends FactoryI<T>> Foo2(F factory) { x = factory. createO;

}

// ...

class IntegerFactory implements FactoryI<Integer> { public Integer createO {

return new Integer(O):

}

}

class Widget {

public static class Factory implements FactoryI<Widget> public Widget createO {

return new Widget О:

public class FactoryConstraint {

public static void main(String[] args) {

new Foo2<Integer>(new IntegerFactoryO); new Foo2<Widget>(new Widget.FactoryO);

}

} ///:-

В

сущности, это всего лишь разновидность передачи Class<T>. В обоих вариантах передаются объекты фабрик; просто в случае с Class<T> объект фабрики оказывается встроенным, а при предыдущем решении он создается явно. Тем не менее в обоих случаях реализуется проверка времени компиляции.

Другое решение основано на использовании паттерна «шаблонный метод». В следующем примере get — шаблонный метод, a create определяется в субклассе для получения объекта этого типа:

//: generics/CreatorGeneriс.java

abstract class GenericWithCreate<T> { final T element;

GenericWithCreateO { element = createO; } abstract T createO;

}

class X {}

class Creator extends GenericWithCreate<X> { X createO { return new XO; } void fO {

System.out.pri nt 1 n(el ement.getClass.getSi mpleName),

}

}

public class CreatorGeneric {

public static void main(String[] args) { Creator с = new CreatorO: C.fO;

}

} /* Output: X

*///:-

Массивы параметризованных типов

Как мы видели в Erased.java, создавать массивы параметризованных типов нельзя. Везде, где возникает необходимость в создании таких массивов, следует применять ArrayList:

//: generics/Li stOfGeneri cs.java import java.util.*;

public class ListOfGenerics<T> {

private List<T> array = new ArrayList<T>;

public void add(T item) { array.add(item); } public T get(int index) { return array.get(index); } } ///:-

При этом вы получаете поведение массивов с безопасностью типов на стадии компиляции, возможной для параметризации.

Впрочем, иногда бывает нужно создать именно массив параметризованных типов (скажем, во внутренней реализации ArrayList используются массивы). Оказывается, можно переопределить ссылку так, чтобы предотвратить протесты компилятора. Пример:

II: tjenerics/ArrayOfGenericReference.java class Generic<T> {}

public class ArrayOfGenericReference { static Generic<Integer>[] gia:

} ///

Компилятор принимает эту запись без каких-либо предупреждений. С другой стороны, вы не сможете создать массив указанного типа (включая параметры типа), поэтому все это сбивает с толку. Поскольку все массивы обладают одинаковой структурой (размер каждого элемента и способ размещения в памяти) независимо от типа хранящихся данных, создается впечатление, что вы сможете создать массив Object и преобразовать его к нужному типу. Код отком-пилируется, но работать не будет — он выдает исключение ClassCastException:

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; // Предупреждение: непроверенное преобразование

Поделиться с друзьями: