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

ЖАНРЫ

Философия Java3

Эккель Брюс

Шрифт:

0

aload_0

1:

aload 1

2:

putfield #2; II Поле obj.Object;

5.

return

public

java lang.Object getO.

0;

aload 0

1-

getfield #2; II Поле obj-Object,

4

areturn

public

static void main(java 1ang.String[]);

0:

new #3, // Класс SimpleHolder

3-

dup

4:

invokespecial #4; // Метод "<init>".V

7.

astore_l

8-

aload 1

9.

ldc #5; II String Item

11

invokevirtual #6; //

Метод set (Object;)V

14:

aload_l

15.

invokevirtual #7, // Метод get:Object:

18;

checkcast #8, //'Класс java/lang/String

21:

astore_2

22.

return

Методы set и get просто записывают и читают значение, а преобразование проверяется в точке вызова get.

Теперь включим параметризацию в приведенный фрагмент:

II: generics/GenericHolder.java

public class GenericHolder<T> { private T obj,

public void set(T obj) { this.obj = obj; } public T get О { return obj; } public static void main(String[] args) { GenericHolder<String> holder =

new GenericHolder<String>; holder.set("Item"); String s = holder.get О;

}

Необходимость преобразования выходного значения get отпала, но мы также знаем, что тип значения, передаваемого set, проверяется во время компиляции. Соответствующий байт-код:

public void set(java.lang.Object);

0:

aload_0

1:

aload_l

2:

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

5:

return

public java.lang.Object getO;

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 считают такие решения нежелательными. Вместо этого рекомендуется использовать явную фабрику и ограничивать тип, чтобы принимался только класс, реализующий эту фабрику:

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