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

866

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

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

Если я определяю следующее:

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

он работает. Есть что-то, что мне не хватает?

  • 43
    use String [] stockArr = (String[]) stock_list.toArray(new String[0]); см. документацию по Java здесь
  • 13
    @ Nishant Тебе не нужно кастовать! String[] stockArr = stock_list.toArray(new String[0]); достаточно.
Показать ещё 1 комментарий
Теги:
arrays
arraylist

6 ответов

1519
Лучший ответ

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

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);
  • 178
    Чтобы объяснить, что здесь происходит, JVM не знает, как слепо понижать Object [] (результат toArray ()) до String []. Чтобы сообщить ему, какой у вас требуемый тип объекта, вы можете передать типизированный массив в toArray (). Типизированный массив может иметь любой размер (допустима новая строка [1]), но если он слишком мал, JVM изменит его размер самостоятельно.
  • 54
    @dhackner - "... JVM не знает, как слепо понижать Object [] до String []" . Точнее, это не разрешено . Если бы это могло сделать это, это нарушило бы безопасность типов Java.
Показать ещё 10 комментариев
865

Попробуйте это

String[] arr = list.toArray(new String[list.size()]);
  • 0
    Object [] obj = stock_list.toArray (); String [] stockArr = new String [obj.length]; for (int i = 0; i <stockArr.length; i ++) {stockArr [i] = (String) obj [i]; }
  • 8
    Для всех, кто интересуется, что происходит в комментарии @ QuanNguyen: он в основном получает Object[] помощью .toArray() а затем вручную копирует содержимое в новую String[] , преобразуя каждый элемент в String . Это плохой подход, и вы должны вместо этого просто передать новую String[] в .toArray()
Показать ещё 4 комментария
244

Что происходит, так это то, что stock_list.toArray() создает Object[] а не String[] и, следовательно, при ошибке типа 1 не выполняется.

Правильный код:

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

или даже

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

(Удивительно, но последняя версия быстрее в последних версиях Java: см. https://stackoverflow.com/questions/4042434/converting-arrayliststring-to-string-in-java)

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

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


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

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

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

  • 5
    Почему это не работает? String [] gs = (String []) stockList.toArray ();
  • 3
    @ Эшвин - это объясняется в первой строке моего Ответа. И если вам нужно легалистическое «почему», то это потому, что именно так javadoc определяет toArray() .
Показать ещё 5 комментариев
119

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

String[] strings = list.stream().toArray(String[]::new);
  • 3
    К вашему сведению, IntelliJ IDEA предлагает изменить его как list.toArray(new String[0]) . Я не знаю почему.
  • 0
    @JinKwon Какая целевая языковая версия вашего проекта?
Показать ещё 1 комментарий
81

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


Почему 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;

Хотя такой код будет скомпилирован, во время выполнения мы увидим throw 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[] рядом с 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 class 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.
  • 3
    Это все правильно, но основная причина проблемы глубже / старше, чем общая типизация. Метод toArray вел себя так с тех пор, как был представлен каркас коллекции. Это предшествует общему количеству лет. (Можно сказать, что дженерики не решили проблему ...)
  • 0
    @StephenC Правда, я сосредоточился только на проблеме дженериков, забыв, что этот инструмент не всегда был частью фреймворка коллекций. Я постараюсь переписать этот ответ, чтобы включить вашу информацию, но не могу этого сделать. Если у вас есть время, не стесняйтесь редактировать его. Также не стесняйтесь, если вы считаете, что я не принадлежу к этому вопросу :)
16

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

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

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

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

public Object[] toArray()

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

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

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

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

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

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

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

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

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

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

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

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

  • 0
    Object [] obj = stock_list.toArray ();
  • 2
    @QuanNguyen: что ты говоришь? Какую точку вы пытаетесь сделать?
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню