В 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>
s, которые на самом деле содержат строки. Поэтому, чтобы предупредить вас, что нужно быть осторожным, у них был компилятор
но выдает предупреждение «непроверенный актерский состав» всякий раз, когда вы пишете такой актерский состав, напоминая вам, что есть подозрительный актерский состав, и вы должны доказать, что это правильно. В основах кода, над которыми я работал, непроверенные приведения должны быть помечены @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