#java #generics
#java #обобщения
Вопрос:
Почему следующий код потенциально не является типобезопасным (компилятор выдает предупреждение)?
class ArrayTypeErasure<T> {
private T[] elements;
public void setElements(List<T> elements) {
this.elements = (T[]) elements.toArray();
}
}
Я пытаюсь придумать любые ситуации, когда код мог бы завершиться ошибкой, но пока что сбой происходит только у меня.
Ответ №1:
Во-первых, обратите внимание, что массивы знают тип своего компонента во время выполнения, но экземпляры универсальных классов не знают аргумент универсального типа во время выполнения. List<T>
Не может создать объект массива во время выполнения с типом времени выполнения T[]
без дополнительной информации, поскольку он не знает, что T
такое во время выполнения. List#toArray()
Метод, который принимает один параметр массива, использует тип среды выполнения переданного экземпляра массива для построения массива того же типа компонента во время выполнения. Но List#toArray()
без параметров всегда создается массив с типом среды выполнения Object[]
. Таким образом, elements.toArray()
вычисляется экземпляр массива, который всегда имеет тип времени выполнения Object[]
.
Object[]
не является подтипом T[]
(когда T
это не Object
так), поэтому присвоение этому массиву this.elements
типа во время компиляции T[]
является неправильным. Однако это не приводит сразу к каким-либо исключениям, поскольку удаление T
есть Object
, поэтому удаление T[]
есть Object[]
, и назначение Object[]
для Object[]
нормально. Это не вызовет никаких проблем, если вы убедитесь, что никогда не предоставляете объект, содержащийся в this.elements
, вне этого объекта как T[]
. Однако, если вы предоставляете объект, содержащийся в, this.elements
как тип T[]
, вне класса (например, метод, который возвращает this.elements
как тип T[]
, или если вы создаете this.elements
общедоступное или защищенное поле), вызывающий объект вне класса может ожидать, T
что он будет определенного типа, и это может вызвать исключение приведения класса.
Например, если у вас есть метод, который возвращает this.elements
как тип T[]
:
public T[] getElements() {
return this.elements;
}
и тогда у вас есть вызывающий объект, который удерживает, ArrayTypeErasure<String>
и он вызывает .getElements()
его, он будет ожидать String[]
. Когда он пытается присвоить результат a String[]
, это вызовет исключение приведения к классу, поскольку тип среды выполнения объекта является Object[]
:
ArrayTypeErasure<String> foo = ...
String[] bar = foo.getElements();
Ответ №2:
Просто потому, что List#toArray()
возвращает Object[]
so, от этого метода нет гарантии, что он вернет T[]
Теперь, на практике, все в порядке, поскольку вы всегда знаете, что он вернет требуемый тип
Вы могли бы использовать @SuppressWarnings("unchecked")
, чтобы избежать появления этого предупреждения
Комментарии:
1. Закрытие вопросов как дубликатов не повредит, нет необходимости отвечать на такие вопросы снова.
2. то есть
elements
массив можно каким-то образом переписать, скажем, изList<String>[]
исходного вList<Integer>[]
, но переданный список все равно будет содержать элементыList<String>[]
?
Ответ №3:
Из-за удаления типа вы можете преобразовать List<X>
s в List<Y>
s.
ArrayTypeErasure<String> er = new ArrayTypeErasure<>();
ArrayTypeErasure erased = er;
List intList = new List<Integer>(); // compile warnings, but
intList.add(1);
erased.setElements(intList);
Комментарии:
1. Дурачиться с необработанными типами здесь не является реальным аргументом. Это все равно, что сказать «все в Java сломано и небезопасно, потому что существует отражение».
2. @Tom Он спросил о ситуации, в которой это не удалось, я предоставил ее. Как создание универсального массива, так и необработанные типы основаны на стирании типов, поэтому нет причин не поднимать этот вопрос.
3. Вы правы в том, что это может быть проблемой, но другого масштаба, и компиляторы (или IDE) не генерируют предупреждения об использовании универсальных типов, потому что кто-то может их проигнорировать. Таким образом, это не объясняет, почему OP все еще получает предупреждение для
(T[]) elements.toArray()
и действительно ли это может привести к сбою. И ваш пример не объясняет, как создание массива могло завершиться неудачей. Ваш код завершается с ошибкой после того, как массив был создан, а затем неправильно использован из-за деактивации проверки типа.4. @Tom
List#toArray
работает нормально, создание массива никогда не завершится ошибкой. Проблема здесь всегда в приведении.