</spoiler> Несмотря на то что объект kind хранится в виде Class<T>, стирание означает, что фактически он хранится в виде Class без параметра. Следовательно, при выполнении с ним каких-либо операций (например, при создании массива) Array.newInstance() не обладает информацией о типе, подразумеваемой kind. Метод не сможет выдать нужный результат, не требующий преобразования типа, а это приводит к выдаче предупреждения, с которым вам не удастся справиться. Обратите внимание: для создания массивов в параметризованном коде рекомендуется использовать Array.newInstance(). Если вместо массива создается другой контейнер, ситуация меняется:
//: generics/ListMaker.java
import java.util.*;
public class ListMaker<T> {
List<T> create() { return new ArrayList<T>(); }
public static void main(String[] args) {
ListMaker<String> stringMaker= new ListMaker<String>();
List<String> stringList = stringMaker.create();
}
}
Компилятор не выдает предупреждений, хотя мы знаем, что <Т> в new ArrayList<T>() внутри create() удаляется — во время выполнения <Т> внутри класса нет, поэтому здесь его присутствие выглядит бессмысленным. Однако если вы попробуете применить эту идею на практике и преобразуете выражение в new ArrayList(), компилятор выдаст предупреждение. Но действительно ли этот элемент не имеет смысла? Что произойдет, если мы поместим в список несколько объектов, прежде чем возватим его?
//: generics/FilledListMaker.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 FilledListMaker<String>();
List<String> list = stringMaker.create("Hello", 4);
System.out.println(list);
}
}
<spoiler text="Output:">
[Hello, Hello. Hello. Hello]
</spoiler> Хотя компилятор ничего не может знать о Т в create(), он все равно способен проверить — на стадии компиляции — что заносимые в result объекты имеют тип Т и согласуются с ArrayList<T>. Таким образом, несмотря на то что стирание удаляет информацию о фактическом типе внутри метода или класса, компилятор все равно может проверить корректность использования типа в методе или классе. Так как стирание удаляет информацию о типе внутри тела метода, на стадии выполнения особую роль приобретают границы — точки, в которых объект входит и выходит из метода. Именно в этих точках компилятор выполняет проверку типов и вставляет код преобразования. Рассмотрим следующий параметризованный пример:
//: 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.get();
}
}
Декомпилировав результат командой javap -с SimpleHolder, мы получим (после редактирования): <spoiler text="Byte-code:">
public void set(java lang Object);
0: aload_0
1: aload 1
2: putfield #2; II Поле obj.Object;
5: return public java lang.Object get().
0: aload 0
1: getfield #2; II Поле obj-Object,
4: areturn public static void main(java lang.StringE]);
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
</spoiler> Методы set() и get() просто записывают и читают значение, а преобразование проверяется в точке вызова get(). Теперь включим параметризацию в приведенный фрагмент:
//: 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(), проверяется во время компиляции. Соответствующий байт-код: <spoiler text="Byte-code:">
public void set(java.lang.Object);
0:aload_0
1:aload_l
2:putfield #2: // Поле obj:0bject:
5:return public java.lang.Object get():
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:()Object:
18:checkcast #8: // Класс java/lang/String
21:astore_2
22:return
</spoiler> Как видите, байт-код идентичен. Дополнительная работа по проверке входного типа set() выполняется компилятором «бесплатно». Преобразование выходного значения get() по-прежнему сохранилось, но, по крайней мере, вам не приходится выполнять его самостоятельно — оно автоматически вставляется компилятором.