Удаление типа массива

#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 работает нормально, создание массива никогда не завершится ошибкой. Проблема здесь всегда в приведении.