Java дженерики в ArrayList.toArray()
скажем, у вас есть arraylist определяется следующим образом:
ArrayList<String> someData = new ArrayList<>();
позже в вашем коде, из-за дженериков вы можете сказать следующее:
String someLine = someData.get(0);
и компилятор знает прямо, что он будет получать строку. Ура дженерики! Однако это не удастся:
String[] arrayOfData = someData.toArray();
toArray()
всегда будет возвращать массив объектов, а не универсального, который был определен. Почему это get(x)
метод знает, что он возвращает, но toArray()
по умолчанию для объектов?
7 ответов:
если вы посмотрите на реализацию
toArray(T[] a)
на ArrayListкласс, это как: public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }
проблема с этим методом заключается в том, что вам нужно передать массив одного и того же универсального типа. Теперь рассмотрим, если этот метод не принимает никаких аргументов, то реализация будет чем-то похожим на:
public <T> T[] toArray() { T[] t = new T[size]; // compilation error return Arrays.copyOf(elementData, size, t.getClass()); }
но проблема здесь в том, что вы не можете создавать универсальные массивы в Java потому что компилятор не знает, что именно
T
представляет. Другими словами создание массива невоспроизводимого типа (JLS §4.7)не допускается в Java.еще одна важная цитата из Исключение Хранилища Массивов (JLS §10.5):
если тип компонента массива не был reifiable (§4.7), виртуальная машина Java не могла выполнить проверку хранилища, описанную в предыдущий пункт. Вот почему массив создание выражения с помощью недопустимый тип элемента запрещен (§15.10.1).
именно поэтому Java предоставил перегруженную версию
toArray(T[] a)
.Я переопределю метод toArray (), чтобы сообщить ему, что он вернет массив Э.
так что вместо переопределения
toArray()
, вы должны использоватьtoArray(T[] a)
.не удается создать экземпляры параметров типа из Java Doc может также будет интересно для вас.
Общая информация удалены во время выполнения. JVM не знает, является ли ваш список
List<String>
илиList<Integer>
(во время выполненияT
наList<T>
решается какObject
), поэтому единственным возможным типом массива являетсяObject[]
.можно использовать
toArray(T[] array)
хотя - в этом случае JVM может использовать класс из данного массива, вы можете увидеть его вArrayList
реализация:public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass());
если вы посмотрите на Javadoc для
List
интерфейс, вы заметите вторую формуtoArray
:<T> T[] toArray(T[] a)
.на самом деле, Javadoc даже дает пример того, как делать именно то, что вы хотите сделать:
String[] y = x.toArray(new String[0]);
уместно отметить, что массивы в Java знают свой тип компонента во время выполнения.
String[]
иInteger[]
разные классы во время выполнения, и вы можете задать массивы для их типа компонента во время выполнения. Поэтому тип компонента необходим во время выполнения (либо путем жесткого кодирования reifiable типа компонента во время компиляции сnew String[...]
, или с помощьюArray.newInstance()
и передаче объекта класса) для создания массива.С другой стороны, аргументы типа в обобщениях не существуют во время выполнения. Нет абсолютно никакой разницы во времени выполнения между
ArrayList<String>
иArrayList<Integer>
. Это все простоArrayList
.это основная причина, почему вы не можете просто сделать
List<String>
и получаетString[]
без передачи типа компонента отдельно каким-либо образом-вам нужно будет получить информацию о типе компонента из чего-то, что не имеет информации о типе компонента. Ясно, что это невозможно.
я могу и буду использовать итератор вместо того, чтобы иногда создавать массив, но это всегда казалось мне странным. Почему метод get (x) знает, что он возвращает, но toArray () по умолчанию использует объекты? Его как на полпути к проектированию они решили, что это не нужно здесь??
поскольку намерение вопроса, похоже, не только о том, чтобы обойти использование
toArray()
с дженериками, а также о понимании дизайна методов вArrayList
класс, я хотел бы добавить:
ArrayList
является универсальным классом, поскольку он объявлен какpublic class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
что позволяет использовать общие методы, такие как
public E get(int index)
внутри класса.но если такой метод, как
toArray()
не возвращаетE
, аE[]
тогда все становится немного сложнее. Было бы невозможно предложить подпись, такую какpublic <E> E[] toArray()
потому что невозможно создать универсальный массивов.создание массивы происходят во время выполнения и из-за стирания типа, Java runtime не имеет конкретной информации типа, представленного
E
. Единственным обходным путем на данный момент является передача требуемого типа в качестве параметра метода и, следовательно, сигнатурыpublic <T> T[] toArray(T[] a)
где клиенты вынуждены передавать требуемый тип.но с другой стороны, это работает для
public E get(int index)
потому что если вы посмотрите на реализацию метода, вы обнаружите, что даже если метод использует из того же массива объекта, чтобы вернуть элемент по указанному индексу, он приводится кE
E elementData(int index) { return (E) elementData[index]; }
это компилятор Java, который во время компиляции заменяет
E
СObject
массив имеет другой тип, чем тип массива. Это своего рода класс StringArray вместо класса String.
предполагая, что это было бы возможно, общий метод
toArray()
будет выглядеть какprivate <T> T[] toArray() { T[] result = new T[length]; //populate return result; }
теперь во время компиляции тип T стирается. Как должна быть часть
new T[length]
заменить? Общие сведения о типе недоступны.если вы посмотрите на исходный код (например,)
ArrayList
, вы увидите то же самое. ЭлементtoArray(T[] a)
способ либо заполняет данный массив (если размер совпадает), либо создает новый новый массив с помощью тип параметра, который является типом массива универсального типа T.
самое первое, что вы должны понять, что
ArrayList
собственный-это просто массивObject
transient Object[] elementData;
когда дело доходит до причин, почему
T[]
это ошибка, потому что вы не можете получить массив универсального типа безClass<T>
и это потому, что тип java erase (есть еще объяснение и как создать один). А тоarray[]
в куче знает свой тип динамически и вы не можете броситьint[]
доString[]
. По той же причине, что и вы не могу броситьObject[]
доT[]
.int[] ints = new int[3]; String[] strings = (String[]) ints;//java: incompatible types: int[] cannot be converted to java.lang.String[] public <T> T[] a() { Object[] objects = new Object[3]; return (T[])objects; } //ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; Integer[] a = new LearnArray().<Integer>a();
а что вы вкладываете в
array
это просто объект, тип которогоE
(который проверяется компилятором), так что вы можете просто бросить его вE
что безопасно и правильно.return (E) elementData[index];
короче говоря, вы не можете получить то, что не имеют по актерскому составу. У вас просто
Object[]
, так чтоtoArray()
может просто вернутьсяObject[]
(в противном случае, вы должны дать емуClass<T>
чтобы создать новый массив с этим типом). Вы ставитеE
наArrayList<E>
, вы можете получитьE
Сget()
.