Подтвердить что ты не робот

Список 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);
}
}

Есть ли причина, почему это объявлено таким образом?

4b9b3361

Ответ 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);

который после стирания типа имеет ту же подпись, что и исходный не общий метод.