Философия Java3
Шрифт:
II: generics/Fi1ledListMaker java
import java.util.*;
public class FilledListMaker<T> { List<T> create(T t, int n) {
List<T> result = new ArrayList<T>; for(int i = 0: i < n; i++)
result.add(t); return result:
}
public static void main(String[] args) {
FilledListMaker<String> stringMaker =
new Fi11edListMaker<String>; List<String> list = stringMaker.createC'Hello", 4): System.out.pri ntln(1i st);
}
} /* Output:
[Hello, Hello. Hello. Hello]
*lll:~
Хотя
Так как стирание удаляет информацию о типе внутри тела метода, на стадии выполнения особую роль приобретают границы — точки, в которых объект входит и выходит из метода. Именно в этих точках компилятор выполняет проверку типов и вставляет код преобразования. Рассмотрим следующий ^параметризованный пример:
// generics/SimpleHolder.java
public class SimpleHolder { private Object obj;
public void set(Object obj) { this.obj = obj; } public Object get О { return obj; } public static void main(String[] args) {
SimpleHolder holder = new SimpleHolder.
holder set("Item"),
String s = (String)holder getO:
}
} ///-
Декомпилировав результат командой javap -с SimpleHolder, мы получим (после редактирования):
public
void set(java lang Object);
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
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: