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

Шрифт:

-
+

Интервал:

-
+

Закладка:

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

0:

aload_0

1:

getfield #2; // Поле obj:0bject;

4:

areturn

public static void main(java.lang.String[]);

0.

new #3; // Класс GenericHolder

3:

dup

4:

invokespecial #4; // Метод "<init>"-()V

7:

astore_l

8:

aload_l

9:

ldc #5; // String Item

11

invokevirtual #6; II Метод set:(Object;)V

14

aload_l

15

invokevirtual #7; // Метод get:OObject:

18

checkcast #8; // Класс java/lang/String

21

astore_2

22

return

Как видите, байт-код идентичен. Дополнительная работа по проверке входного типа set() выполняется компилятором «бесплатно». Преобразование выходного значения get() по-прежнему сохранилось, но, по крайней мере, вам не приходится выполнять его самостоятельно — оно автоматически вставляется компилятором.

Компенсация за стирание

Как мы видели, в результате стирания становится невозможным выполнение некоторых операций в параметризованном коде. Все, для чего необходима точная информация о типе во время выполнения, работать не будет:

//: generics/Erased.java // {CompileTimeError} (He компилируется)

public class Erased<T> {

private final int SIZE = 100: public static void f(Object arg) { if(arg instanceof T) {} T var = new TO; T[] array = new T[SIZE]; T[] array = (T)new Object[SIZE]

}

} Hi

ll Ошибка 11 Ошибка II Ошибка ; 11 Предупреждение

Иногда такие проблемы удается обойти на программном уровне, но в отдельных случаях стирание приходится компенсировать посредством введения метки типа. Другими словами, вы явно передаете объект Class для своего типа.

Например, попытка использования instanceof в предыдущем примере завершилась неудачей из-за того, что информация о типе была стерта. При введении метки типа вместо instanceof можно использовать динамический метод islnstance():

//: generics/ClassTypeCapture.java

class Building {}

class House extends Building {}

public class ClassTypeCapture<T> { Class<T> kind;

public ClassTypeCapture(Class<T> kind) { this.kind = kind;

}

public boolean f(Object arg) {

return kind.islnstance(arg);

}

public static void main(String[] args) { ClassTypeCapture<Building> cttl =

new CIassTypeCapture<Bui1di ng>(Bui 1di ng.class); System.out.pri nt1n(cttl.f(new Bui 1di ng())); System.out.pri ntin(cttl.f(new House())); ClassTypeCapture<House> ctt2 =

new ClassTypeCapture<House>(House.class); System.out.pri nt1n(ctt2.f(new Bui 1di ng())); System.out.pri nt1n(ctt2.f(new House()));

}

} /* Output; true true false true *///:-

Компилятор следит за тем, чтобы метка типа соответствовала обобщенному аргументу.

Создание экземпляров типов

Попытка создания newT() в Erased.java не работает отчасти из-за стирания, а отчасти из-за того, что компилятор не может убедиться в наличии у Т конструктора по умолчанию (без аргументов). Но в С++ эта операция естественна, прямолинейна и безопасна (проверка выполняется во время компиляции):

//; generics/InstantiateGenericType.java import static net.mindview.util.Print.*;

class ClassAsFactory<T> { T x;

public ClassAsFactory(Class<T> kind) {

try { продолжение &

х = kind.newInstanceO; } catch(Exception е) {

throw new RuntimeException(e);

}

}

}

class Employee {}

public class InstantiateGenericType {

public static void main(String[] args) { ClassAsFactory<Employee> fe =

new ClassAsFactory<Employee>(Employee.class); pri nt("ClassAsFactory<Employee> успех"); try {

ClassAsFactory<Integer> fi =

new ClassAsFactory<Integer>(Integer.class); } catch(Exception e) {

print("ClassAsFactory<Integer> неудача");

}

}

} /* Output:

ClassAsFactory<Employee> успех ClassAsFactory<Integer> неудача *///:-

Программа компилируется, но с ClassAsFactory<Integer> происходит сбой, так как Integer не имеет конструктора по умолчанию. Ошибка не обнаруживается во время компиляции, поэтому специалисты из Sun считают такие решения нежелательными. Вместо этого рекомендуется использовать явную фабрику и ограничивать тип, чтобы принимался только класс, реализующий эту фабрику:

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

}

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); }

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

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