it-swarm.com.ru

Преобразовать ArrayList <String> в массив String []

Я работаю в среде Android и ​​пробовал следующий код, но, похоже, он не работает.

String [] stockArr = (String[]) stock_list.toArray();

Если я определю следующим образом:

String [] stockArr = {"hello", "world"};

оно работает. Я что-то упускаю?

1065
locoboy

Используйте как это.

List<String> stockList = new ArrayList<String>();
stockList.add("stock1");
stockList.add("stock2");

String[] stockArr = new String[stockList.size()];
stockArr = stockList.toArray(stockArr);

for(String s : stockArr)
    System.out.println(s);
1702
Prince John Wesley

Попробуй это

String[] arr = list.toArray(new String[list.size()]);
918
st0le

Происходит то, что stock_list.toArray() создает Object[], а не String[], и, следовательно, происходит сбой при типе1,.

Правильный код будет:

  String [] stockArr = stockList.toArray(new String[stockList.size()]);

или даже

  String [] stockArr = stockList.toArray(new String[0]);

Для получения дополнительной информации обратитесь к javadocs для двух перегрузок List.toArray.

Последняя версия использует массив нулевой длины для определения типа результирующего массива. (Удивительно, но это быстрее сделать, чем предварительно распределить ... по крайней мере, для последних Java выпусков. См. https://stackoverflow.com/a/4042464/139985 для деталей.)

С технической точки зрения, причина такого поведения/дизайна API заключается в том, что реализация метода List<T>.toArray() не имеет информации о том, что такое <T> во время выполнения. Все, что он знает, это то, что необработанный тип элемента - Object. Напротив, в другом случае параметр массива дает базовый тип массива. (Если предоставленный массив является достаточно большим, чтобы содержать элементы списка, он используется. В противном случае новый массив того же типа и большего размера выделяется и возвращается в качестве результата.)


1 - В Java Object[] не совместим с присвоением String[]. Если бы это было так, то вы могли бы сделать это:

    Object[] objects = new Object[]{new Cat("fluffy")};
    Dog[] dogs = (Dog[]) objects;
    Dog d = dogs[0];     // Huh???

Это явно бессмыслица, и именно поэтому типы массивов обычно не совместимы по присваиванию.

296
Stephen C

Альтернатива в Java 8:

String[] strings = list.stream().toArray(String[]::new);
144
Vitalii Fedorenko

Я вижу много ответов, показывающих, как решить проблему, но только ответ Стивена пытается объяснить, почему возникает проблема, поэтому я попытаюсь добавить что-то еще по этому вопросу. Это история о возможных причинах, по которым Object[] toArray не был изменен на T[] toArray, где дженерики были представлены в Java.


Почему String[] stockArr = (String[]) stock_list.toArray(); не работает?

В Java универсальный тип существует только во время компиляции . Во время выполнения информация об универсальном типе (как в вашем случае <String>) удаляется и заменяется типом Object (посмотрите стирание типа ). Вот почему во время выполнения toArray() не имеет представления о том, какой именно тип использовать для создания нового массива, поэтому он использует Object как самый безопасный тип, поскольку каждый класс расширяет Object, чтобы он мог безопасно хранить экземпляр любого класса.

Теперь проблема в том, что вы не можете привести экземпляр Object[] к String[].

Зачем? Взгляните на этот пример (предположим, что class B extends A):

//B extends A
A a = new A();
B b = (B)a;

Хотя такой код будет компилироваться, во время выполнения мы увидим выброшенный ClassCastException, поскольку экземпляр, содержащийся по ссылке a, на самом деле не имеет типа B (или его подтипов). Почему эта проблема (почему это исключение должно быть приведено)? Одна из причин заключается в том, что у B могут быть новые методы/поля, которых нет у A, поэтому возможно, что кто-то попытается использовать эти новые члены с помощью ссылки b, даже если удерживаемый экземпляр не имеет (не поддерживает) их , Другими словами, мы можем попытаться использовать данные, которые не существуют, что может привести ко многим проблемам. Поэтому, чтобы предотвратить такую ​​ситуацию, JVM выдает исключение и останавливает потенциально опасный код.

Теперь вы можете спросить: "Так почему же мы не остановились еще раньше? Почему код, включающий такое преобразование, даже компилируем? Не должен ли компилятор остановить это?". Ответ: нет, потому что компилятор не может точно знать, каков фактический тип экземпляра, хранящегося по ссылке a, и есть вероятность, что он будет содержать экземпляр класса B, который будет поддерживать интерфейс ссылки b. Взгляните на этот пример:

A a = new B(); 
      //  ^------ Here reference "a" holds instance of type B
B b = (B)a;    // so now casting is safe, now JVM is sure that `b` reference can 
               // safely access all members of B class

Теперь давайте вернемся к вашим массивам. Как вы видите в вопросе, мы не можем привести экземпляр массива Object[] к более точному типу String[]

Object[] arr = new Object[] { "ab", "cd" };
String[] arr2 = (String[]) arr;//ClassCastException will be thrown

Здесь проблема немного другая. Теперь мы уверены, что массив String[] не будет иметь дополнительных полей или методов, потому что каждый массив поддерживает только:

  • оператор [],
  • length подано,
  • методы, унаследованные от супертипа Object,

Так что это не интерфейс массивов, что делает это невозможным. Проблема в том, что Object[] массив рядом с Strings может хранить любые объекты (например, Integers), так что это возможно в один прекрасный день мы в конечном итоге попытаемся вызвать метод наподобие strArray[i].substring(1,3) в экземпляре Integer, у которого такого метода нет.

Таким образом, чтобы быть уверенным , что такая ситуация никогда не произойдет, в Java могут храниться только ссылки на массивы

  • экземпляры массива того же типа, что и ссылка (ссылка String[] strArr может содержать String[])
  • экземпляры массива подтипа (Object[] может содержать String[], потому что String является подтипом Object),

но не могу удержаться

  • массив супертипа типа массива из ссылки (String[] не может содержать Object[])
  • массив типа, который не связан с типом из ссылки (Integer[] не может содержать String[])

Другими словами что-то вроде этого нормально

Object[] arr = new String[] { "ab", "cd" }; //OK - because
               //  ^^^^^^^^                  `arr` holds array of subtype of Object (String)
String[] arr2 = (String[]) arr; //OK - `arr2` reference will hold same array of same type as 
                                //     reference

Можно сказать, что один из способов решения этой проблемы - найти во время выполнения наиболее распространенный тип среди всех элементов списка и создать массив этого типа, но это не сработает в ситуациях, когда все элементы списка будут одного типа, производного от универсального. Взглянуть

//B extends A
List<A> elements = new ArrayList<A>();
elements.add(new B());
elements.add(new B());

в настоящее время наиболее распространенным типом является B, а не A, поэтому toArray()

A[] arr = elements.toArray();

будет возвращать массив B класса new B[]. Проблема с этим массивом состоит в том, что, хотя компилятор позволит вам редактировать его содержимое, добавив к нему элемент new A(), вы получите ArrayStoreException, поскольку массив B[] может содержать только элементы класса B или его подкласса, чтобы гарантировать, что все элементы будут поддерживать интерфейс интерфейса. B, но экземпляр A может иметь не все методы/поля B. Так что это решение не идеально.


Лучшее решение этой проблемы - явно указать, какой тип массива toArray() должен быть возвращен путем передачи этого типа в качестве аргумента метода, например

String[] arr = list.toArray(new String[list.size()]);

или же

String[] arr = list.toArray(new String[0]); //if size of array is smaller then list it will be automatically adjusted.
84
Pshemo

Правильный способ сделать это:

String[] stockArr = stock_list.toArray(new String[stock_list.size()]);

Я хотел бы добавить к другим отличным ответам здесь и объяснить, как вы могли бы использовать Javadocs, чтобы ответить на ваш вопрос.

Javadoc для toArray() (без аргументов): здесь . Как видите, этот метод возвращает Object[] и not String[], который является массивом типа времени выполнения вашего списка:

public Object[] toArray()

Возвращает массив, содержащий все элементы в этой коллекции. Если коллекция дает какие-либо гарантии относительно того, в каком порядке ее элементы возвращаются ее итератором, этот метод должен возвращать элементы в том же порядке. Возвращенный массив будет "безопасным" в том смысле, что коллекция не поддерживает никаких ссылок на него. (Другими словами, этот метод должен выделять новый массив, даже если коллекция поддерживается массивом). Таким образом, вызывающая сторона может модифицировать возвращаемый массив.

Прямо под этим методом, однако, Javadoc для toArray(T[] a). Как вы можете видеть, этот метод возвращает T[], где T - это тип массива, который вы передаете. Сначала это выглядит как то, что вы ищете, но неясно, почему именно вы передаете массив (вы добавляете к нему, используя его только для типа и т. д.). Документация поясняет, что цель переданного массива, по сути, состоит в том, чтобы определить тип возвращаемого массива (что в точности соответствует вашему варианту использования):

public <T> T[] toArray(T[] a)

Возвращает массив, содержащий все элементы в этой коллекции; тип времени выполнения возвращаемого массива соответствует типу указанного массива. Если коллекция помещается в указанный массив, она возвращается в него. В противном случае новый массив выделяется с типом времени выполнения указанного массива и размером этой коллекции. Если коллекция помещается в указанный массив с достаточным пространством для резервирования (т. Е. Массив имеет больше элементов, чем коллекция), элемент в массиве, следующий сразу за концом коллекции, устанавливается равным нулю. Это полезно при определении длины коллекции, только если вызывающая сторона знает, что коллекция не содержит нулевых элементов.)

Если эта коллекция дает какие-либо гарантии относительно того, в каком порядке ее элементы возвращаются ее итератором, этот метод должен возвращать элементы в том же порядке.

Эта реализация проверяет, является ли массив достаточно большим, чтобы содержать коллекцию; если нет, он выделяет новый массив правильного размера и типа (используя отражение). Затем он выполняет итерацию по коллекции, сохраняя каждую ссылку на объект в следующем последовательном элементе массива, начиная с элемента 0. Если массив больше, чем коллекция, то нулевое значение сохраняется в первом месте после окончания коллекции.

Конечно, понимание дженериков (как описано в других ответах) необходимо, чтобы действительно понять разницу между этими двумя методами. Тем не менее, если вы сначала пойдете в Javadocs, вы, как правило, найдете свой ответ, а затем сами убедитесь, что еще вам нужно выучить (если вы действительно это делаете).

Также обратите внимание, что чтение Javadocs поможет вам понять, какой должна быть структура массива, который вы передаете. Хотя это может на самом деле не иметь большого значения, вы не должны передавать пустой массив следующим образом:

String [] stockArr = stockList.toArray(new String[0]);  

Поскольку из документа эта реализация проверяет, является ли массив достаточно большим, чтобы содержать коллекцию; если нет, он выделяет новый массив правильного размера и типа (с использованием отражения). Нет необходимости в дополнительных издержках при создании нового массива, когда вы легко можете передать размер.

Как обычно бывает, Javadocs предоставляют вам множество информации и указаний.

Эй, подожди минутку, что это за отражение?

20
Rick Hanlon II