将ArrayList <String>转换为String []数组

loc*_*boy 1102 java arrays arraylist

我正在android环境中工作,并尝试了以下代码,但它似乎没有工作.

String [] stockArr = (String[]) stock_list.toArray();
Run Code Online (Sandbox Code Playgroud)

如果我定义如下:

String [] stockArr = {"hello", "world"};
Run Code Online (Sandbox Code Playgroud)

有用.有什么东西我不见了吗?

Pri*_*ley 1730

像这样使用.

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);
Run Code Online (Sandbox Code Playgroud)

  • 为了解释这里发生了什么,JVM不知道如何盲目地将Object [](toArray()的结果)向下转换为String [].要让它知道您想要的对象类型是什么,您可以将类型化数组传递给toArray().类型化数组可以是任何大小(新的String [1]有效),但如果它太小,那么JVM将自己调整大小. (190认同)
  • @dhackner - *"...... JVM不知道如何盲目地将Object []向下转换为String []"*.或者更确切地说,**不允许**这样做.如果它可以做到这一点,它将违反Java类型的安全性. (60认同)
  • 据Effective Java中的Joshua Bloch说,预先分配数组会损害性能.提供零长度数组.`stockList.toArray(new String [0])` (8认同)
  • 对于noobies来说,如果你的数组列表包含`double,float,ints,longs`(原始类型),你必须将你的数组/ arraylist定义为包含一个原始类型对象,例如`Double,Float,Integer, Long`.然后从那里你的普通数组必须用这种类型定义,例如`Double [] myDoubleArray = list.toArray(new Double [listOfDoubles.size()]); (3认同)
  • 使用 `stock_list.toArray(stockArr)` 而不是 `stockArr = stock_list.toArray(stockArr)`。见 http://stackoverflow.com/a/9572820/597657 (2认同)

st0*_*0le 932

试试这个

String[] arr = list.toArray(new String[list.size()]);
Run Code Online (Sandbox Code Playgroud)

  • 你不需要传递`list.size()`,这只会创建一个更大的临时数组的开销,它将被立即丢弃.我可以只是`String [] arr = list.toArray(new String [] {});`.由于简单的oneliner答案仍然给+1. (17认同)
  • 要注意`toArray(new String [0])`比[toArray(new String [list.size()]更[实际上更快](https://shipilev.net/blog/2016/arrays-wisdom-ancients/) )`:http://stackoverflow.com/questions/4042434/converting-arrayliststring-to-string-in-java (15认同)
  • @LeoHolanda,如果参数中提供的数组符合列表,则使用相同的数组,如果大小不合适,则会自行分配数组,在您的情况下,最终会创建一个虚拟数组(大小为0) . (10认同)
  • 对于任何想知道@ QuanNguyen评论中发生了什么的人:他基本上通过`.toArray()`检索`Object []`然后手动将内容复制到一个新的`String []`,将每个元素转换为` String`.*这是一个糟糕的方法,你应该只是将新的`String []`传递给`.toArray()`* (9认同)

Ste*_*n C 304

正在发生的stock_list.toArray()是创建一个Object[]而不是一个String[],因此类型转换失败1.

正确的代码是:

  String [] stockArr = stockList.toArray(new String[stockList.size()]);
Run Code Online (Sandbox Code Playgroud)

甚至

  String [] stockArr = stockList.toArray(new String[0]);
Run Code Online (Sandbox Code Playgroud)

有关更多详细信息,请参阅两个重载的javadoc List.toArray.

后一版本使用零长度数组来确定结果数组的类型.(令人惊讶的是,执行此操作要比预先分配...至少对于最近的Java版本更快.有关详细信息,请参阅/sf/answers/282972511/.)

从技术角度来看,这种API行为/设计的原因是该List<T>.toArray()方法的实现没有<T>运行时的信息.它只知道原始元素类型是Object.相反,在另一种情况下,数组参数给出了数组的基本类型.(如果提供的数组大到足以容纳列表元素,则使用它.否则将分配相同类型和更大大小的新数组并作为结果返回.)


1 - 在Java中,Object[]a与赋值不兼容String[].如果是,那么你可以这样做:

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

这显然是无稽之谈,这就是为什么数组类型通常不是赋值兼容的原因.

  • 如果你的意思是,它可以将 `List&lt;Integer&gt;` 转换为 `int[]` ......这是正确的。它将其转换为`Integer[]`。 (3认同)
  • 这不适用于原始类型. (2认同)

Vit*_*nko 151

Java 8中的替代方案:

String[] strings = list.stream().toArray(String[]::new);
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考,IntelliJ IDEA建议将其更改为“ list.toArray(new String [0])”。我不知道为什么 (8认同)

Psh*_*emo 87

我可以看到许多答案显示如何解决问题,但只有斯蒂芬的答案是试图解释为什么会出现问题所以我会尝试在这个问题上添加更多内容.这是一个关于可能的原因的故事,为什么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;
Run Code Online (Sandbox Code Playgroud)

虽然这样的代码会编译,但在运行时我们会看到抛出,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
Run Code Online (Sandbox Code Playgroud)

现在让我们回到你的阵列.正如你在问题中看到的,我们不能投的实例Object[]阵列更精确的类型String[]喜欢

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

这里的问题有点不同.现在我们确定该String[]数组不会有其他字段或方法,因为每个数组仅支持:

  • [] 运营商,
  • length 提交
  • 从Object supertype继承的方法,

所以不是阵列接口使它变得不可能.问题是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
Run Code Online (Sandbox Code Playgroud)

你可以说解决这个问题的一种方法是在运行时找到所有列表元素之间最常见的类型并创建该类型的数组,但这不适用于列表的所有元素都是派生自通用类型的一种类型的情况.看一看

//B extends A
List<A> elements = new ArrayList<A>();
elements.add(new B());
elements.add(new B());
Run Code Online (Sandbox Code Playgroud)

现在最常见的类型是B,不是A这样toArray()

A[] arr = elements.toArray();
Run Code Online (Sandbox Code Playgroud)

会返回B类的数组new B[].这个数组的问题在于,虽然编译器允许你通过向new A()它添加元素来编辑它的内容,但是你会得到ArrayStoreException因为B[]数组只能包含类B或其子类的元素,以确保所有元素都支持接口B,但实例是A可能没有所有的方法/领域B.所以这个解决方案并不完美.


这个问题的最佳解决方案是toArray()通过将此类型作为方法参数传递来明确告诉应该返回什么类型的数组

String[] arr = list.toArray(new String[list.size()]);
Run Code Online (Sandbox Code Playgroud)

要么

String[] arr = list.toArray(new String[0]); //if size of array is smaller then list it will be automatically adjusted.
Run Code Online (Sandbox Code Playgroud)

  • 这一切都是正确的,但问题的根本原因是比通用类型更深/更旧.自从引入了集合框架以来,`toArray`方法的行为就像这样.它早于仿制药已有好几年了.(你可以说,仿制药没有解决问题...) (3认同)

Ric*_* II 21

正确的方法是:

String[] stockArr = stock_list.toArray(new String[stock_list.size()]);
Run Code Online (Sandbox Code Playgroud)

我想在这里添加其他很好的答案并解释你如何使用Javadocs来回答你的问题.

toArray()(没有参数)的Javadoc 就在这里.如您所见,此方法返回一个Object[]不是 String[]列表的运行时类型的数组:

public Object[] toArray()

返回包含此collection中所有元素的数组.如果集合对其迭代器返回的元素的顺序做出任何保证,则此方法必须以相同的顺序返回元素.返回的数组将是"安全的",因为集合不会保留对它的引用.(换句话说,即使集合由数组支持,此方法也必须分配新数组).因此调用者可以自由修改返回的数组.

不过,在该方法的正下方是Javadoc for toArray(T[] a).正如你所看到的,这个方法返回一个T[]where T你是传入的数组的类型.首先,这看起来像你正在寻找的,但它不清楚为什么你传入一个数组(你是否添加它,仅用于类型等).该文档清楚地表明,传递数组的目的主要是定义要返回的数组类型(这正是您的用例):

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

返回包含此collection中所有元素的数组; 返回数组的运行时类型是指定数组的运行时类型.如果集合适合指定的数组,则返回其中.否则,将使用指定数组的运行时类型和此集合的大小分配新数组.如果集合适合指定的数组,并且有空余空间(即,数组的元素多于集合),则紧跟集合结尾的数组中的元素将设置为null.仅当调用者知道集合不包含任何null元素时,这在确定集合的长度时很有用.)

如果此集合对其迭代器返回的元素的顺序做出任何保证,则此方法必须以相同的顺序返回元素.

此实现检查数组是否足够大以包含集合; 如果没有,它会分配一个正确大小和类型的新数组(使用反射).然后,它遍历集合,将每个对象引用存储在数组的下一个连续元素中,从元素0开始.如果数组大于集合,则在集合结束后的第一个位置存储null.

当然,需要理解泛型(如其他答案中所述)才能真正理解这两种方法之间的区别.然而,如果你第一次去Javadocs,你通常会找到你的答案,然后亲自看看你还需要学习什么(如果你真的这样做).

另请注意,在此处阅读Javadoc可帮助您了解传入的数组的结构应该是什么.虽然它可能实际上并不重要,但你不应该像这样传入一个空数组:

String [] stockArr = stockList.toArray(new String[0]);  
Run Code Online (Sandbox Code Playgroud)

因为,从doc,这个实现检查数组是否足够大以包含集合; 如果没有,它会分配一个正确大小和类型的新数组(使用反射).当您可以轻松传入大小时,不需要额外的开销来创建新数组.

通常情况下,Javadocs为您提供了丰富的信息和方向.

嘿等一下,有什么反映?

  • `toArray(new String [0])`更快:http://shipilev.net/blog/2016/arrays-wisdom-ancients/ (5认同)
  • @QuanNguyen:你在说什么?你想要做什么? (2认同)