Список Java <T> T [] toArray (T [] a) реализация
Я просто смотрел на метод, определенный в интерфейсе List: <T> T[] toArray(T[] a)
, и у меня есть вопрос. Почему это общий? Из-за этого метод не является полностью безопасным по типу. Следующий фрагмент кода компилируется, но вызывает ArrayStoreException
:
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
String[] stringArray = list.toArray(new String[]{});
Мне кажется, что если toArray не был общим и принял параметр типа List, было бы лучше.
Я написал пример с игрушкой, и это нормально witout generic:
package test;
import java.util.Arrays;
public class TestGenerics<E> {
private Object[] elementData = new Object[10];
private int size = 0;
public void add(E e) {
elementData[size++] = e;
}
@SuppressWarnings("unchecked")
//I took this code from ArrayList but it is not generic
public E[] toArray(E[] a) {
if (a.length < size)
// Make a new array of a runtime type, but my contents:
return (E[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
public static void main(String[] args) {
TestGenerics<Integer> list = new TestGenerics<Integer>();
list.add(1);
list.add(2);
list.add(3);
//You don't have to do any casting
Integer[] n = new Integer[10];
n = list.toArray(n);
}
}
Есть ли причина, почему это объявлено таким образом?
Ответы
Ответ 1
Из Javadocs:
Как и метод toArray(), этот метод действует как мост между API на основе массива и коллекции. Кроме того, этот метод позволяет точно контролировать тип времени выполнения выходного массива и может при определенных обстоятельствах использоваться для экономии затрат на распределение.
Это означает, что программист контролирует тип массива.
Например, для вашего ArrayList<Integer>
вместо массива Integer[]
может Integer[]
массив Number[]
или Object[]
.
Кроме того, метод также проверяет переданный массив. Если вы передаете массив, в котором достаточно места для всех элементов, метод toArray
повторно использует этот массив. Это означает:
Integer[] myArray = new Integer[myList.size()];
myList.toArray(myArray);
или же
Integer[] myArray = myList.toArray(new Integer[myList.size()]);
имеет тот же эффект, что и
Integer[] myArray = myList.toArray(new Integer[0]);
Обратите внимание, что в более старых версиях Java последняя операция использовала отражение для проверки типа массива, а затем динамически создавала массив правильного типа. Передавая массив правильного размера в первую очередь, отражение не нужно было использовать для размещения нового массива внутри метода toArray
. Это больше не так, и обе версии могут использоваться взаимозаменяемо.
Ответ 2
Он объявляется в общем виде, чтобы вы могли писать код, например
Integer[] intArray = list.toArray(new Integer[0]);
не возвращая массив.
Объявляется с помощью следующей аннотации:
@SuppressWarnings("unchecked")
Другими словами, Java доверяет вам передать параметр массива того же типа, поэтому ваша ошибка не возникает.
Ответ 3
Причина, по которой метод имеет эту подпись, заключается в том, что API toArray
предшествует дженерикам: метод
public Object[] toArray(Object[] a)
был введен еще в Java 1.2.
Соответствующее общее значение, которое заменяет Object
на T
, было введено как 100%-совместимая опция:
public <T> T[] toArray(T[] a)
Изменение подписи для общего типа позволяет вызывающим абонентам избегать приведения: перед Java 5, вызывающим абонентам необходимо было сделать это:
String[] arr = (String[])stringList.toArray(new String[stringList.size()]);
Теперь они могут выполнять один и тот же вызов без приведения:
String[] arr = stringList.toArray(new String[stringList.size()]);
EDIT:
Более "современной" сигнатурой для метода toArray
была бы пара перегрузок:
public <T> T[] toArray(Class<T> elementType)
public <T> T[] toArray(Class<T> elementType, int count)
Это обеспечит более выразительную и в равной степени универсальную альтернативу текущей сигнатуре метода. Существует также эффективная реализация этого метода Array.newInstance(Class<T>,int)
. Однако изменение подписи не было бы обратно совместимым.
Ответ 4
Он безопасен по типу - он не вызывает ClassCastException
. Обычно это означает, что безопасный тип.
ArrayStoreException
отличается. Если вы включили ArrayStoreException
в "небезопасный тип", все массивы в Java не безопасны для типов.
Вы отправили также код ArrayStoreException
. Просто попробуйте:
TestGenerics<Object> list = new TestGenerics<Object>();
list.add(1);
String[] n = new String[10];
list.toArray(n); // ArrayStoreException
Фактически, просто невозможно разрешить пользователю передавать в массиве тип, который они хотят получить, и в то же время не иметь ArrayStoreException
. Поскольку любая сигнатура метода, которая принимает массив некоторого типа, также допускает массивы подтипов.
Так как невозможно избежать ArrayStoreException
, почему бы не сделать его как можно более общим? Чтобы пользователь мог использовать массив некоторого несвязанного типа, если они каким-то образом знают, что все элементы будут экземплярами этого типа?
Ответ 5
Я думаю, что dasblinkenlight, вероятно, правильно, что это связано с генерацией существующего метода, и полная совместимость - это тонкая вещь, которую нужно достичь.
beny23 point тоже очень хорошо - метод должен принимать супертипы E[]
. Можно попробовать
<T super E> T[] toArray(T[] a)
но Java не допускает super
для переменной типа, из-за отсутствия вариантов использования:)
(править: нет, это не очень удобно для super
, см. fooobar.com/questions/80208/...)
Ответ 6
Причина, по которой этот метод такой, какой он есть, в основном исторический.
Существует различие между родовыми классами и типами массивов: тогда как параметры типа универсального класса стираются во время выполнения, тип элементов массивов - нет. Таким образом, во время выполнения JVM не видит разницы между List<Integer>
и List<String>
, но видит разницу между Integer[]
и String[]
! Причиной этого различия является то, что массивы всегда были там, начиная с Java 1.0, тогда как generics, где только добавлено (обратно совместимым способом) в Java 1.5.
API-интерфейс Collections был добавлен в Java 1.2 до введения дженериков. В то время интерфейс List
уже содержал метод
Object[] toArray(Object[] a);
(см. эту копию 1.2 JavaDoc). Это был единственный способ создать массив с указанным пользователем типом среды выполнения: параметр a
служил типом маркера типа, то есть определял тип среды выполнения возвращаемого массива (обратите внимание, что если a
является подклассом B
, A[]
считается подтипом B[]
, хотя List<A>
не является подтипом List<B>
).
Когда в Java 1.5 были введены обобщения, многие существующие методы были сделаны родовыми, а метод toArray
стал
<T> T[] toArray(T[] a);
который после стирания типа имеет ту же подпись, что и исходный не общий метод.