Я не могу понять, почему я не получаю ошибок в коде Java. У меня есть класс, который использует общий тип:
import java.util.*; // For ArrayList
public class Hat<T>
{
public ArrayList<T> convert(String s)
{
T t = (T) s; // Cast happens here
ArrayList<T> list = new ArrayList<T>();
list.add(t);
return list;
}
}
Затем я выполняю код, который, как мне кажется, должен вызвать ошибку:
Hat<Integer> h = new Hat<Integer>();
ArrayList<Integer> iList = h.convert("hello");
Это создает ArrayList из целых чисел, который каким-то образом имеет String в качестве элемента! Это не вызывает никаких ошибок во время выполнения, даже если вы распечатываете ArrayList (он печатает «[привет]»).
Я ожидал, что метод "convert" выдаст ошибку. Почему этого не происходит и возможно ли это сделать? Интересно, что это происходит, когда я пытаюсь вернуть элемент из ArrayList как целое число, но ошибка возникает не из-за метода "convert".




В Java универсальные шаблоны используются только во время компиляции; они «стираются» после проверки типов программы и не влияют на выполнение программы. В частности, во время выполнения нет разницы между ArrayList<Integer> и ArrayList<String> (или ArrayList или чем-либо еще, если на то пошло). После завершения проверки типов ваша программа будет удалена, и программа, которая будет выполняться, будет эквивалентна:
public class Hat
{
public ArrayList convert(String s)
{
Object t = s;
ArrayList list = new ArrayList();
list.add(t);
return list;
}
}
Hat h = new Hat();
ArrayList iList = h.convert("hello");
который ведет себя так, как вы наблюдали.
Возникает вопрос, почему эта программа выполняет проверку типов, когда она явно выдает неверное значение, которое претендует на роль ArrayList<Integer>, но содержит строки? Разве система типов не должна отвергать такие программы?
Ну, есть, за исключением того, что есть большая лазейка: непроверенные приведения. Когда вы выполняете приведение к типу, который включает в себя общий - в вашем случае строку T t = (T) s; - Java не имеет во время выполнения ничего, что можно было бы использовать для проверки правильности преобразования из-за стирания. Разработчики Java мог только что запретили такое приведение, и в этом случае ваша программа не сможет скомпилироваться.
Однако они этого не сделали. Вместо этого они решили разрешить приведение типов, включающих обобщенные типы, и были уверены, что программист, написавший приведение, был умнее компилятора и знал, что приведение сработает. Однако, если вы используете одно из этих приведений, все ставки отключены, и система типов может закончиться, как вы обнаружили, с ArrayList<Integer>, которые на самом деле содержат строки. Чтобы предупредить вас о том, что вам нужно быть осторожным, у них был компилятор
но выдавать предупреждение о "непроверенном приведении" всякий раз, когда вы пишете такое приведение, напоминая вам о подозрительном приведении, и вам нужно доказать его правильность. В кодовых базах, над которыми я работал, непроверенные приведения должны быть аннотированы @SuppressWarning и комментарием, описывающим, почему приведение всегда допустимо.
Так что, если вы используете хочу для работы с неконтролируемыми приведениями и предпочитаете выполнить проверку во время выполнения? В этом случае вам придется самостоятельно программировать проверку времени выполнения. Часто это можно сделать с объектами Class. В вашем случае вы можете добавить дополнительный параметр Class в свой конструктор Hat, который представляет класс, которым, как вы ожидаете, должен быть T, и использовать его для создания типизированного преобразования, которое проверяется во время выполнения:
public class Hat<T>
{
private final Class<? extends T> expectedClass;
public Hat(Class<? extends T> expectedClass)
{
this.expectedClass = expectedClass;
}
public ArrayList<T> convert(String s)
{
T t = expectedClass.cast(s); // This cast will fail at runtime if T isn't String
ArrayList<T> list = new ArrayList<T>();
list.add(t);
return list;
}
}
Тогда ваш позывной должен быть изменен на:
Hat<Integer> h = new Hat<Integer>(Integer.class);
ArrayList<Integer> iList = h.convert("hello"); // throws
Напомним, что универсальные типы - это стертый во время выполнения.