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

我在android环境中工作,并尝试了以下代码,但它似乎不起作用。

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

如果我定义如下:

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

很有效。我是不是遗漏了什么?

1617359 次浏览

试试这个

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

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

正确的代码是:

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

甚至

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

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

后一个版本使用零长度数组来确定结果数组的类型。(令人惊讶的是,这样做比预分配要快…至少对于最近的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???

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

像这样使用。

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);

我可以看到许多答案显示如何解决问题,但只有Stephen的答案试图解释为什么会出现问题,所以我将尝试在这个主题上添加更多内容。这是一个关于为什么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 AA 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 BB 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超类型继承的方法,

所以它是没有数组接口,这是不可能的。问题是#1旁边的#0数组可以存储任何对象(例如Integers),所以有可能在美好的一天,我们最终会尝试在Integer的实例上调用像strArray[i].substring(1,3)这样的方法,而Integer没有这样的方法。

因此,为了使当然发生这种情况从未,在Java数组引用只能保存

  • 与引用类型相同的数组实例(引用String[] strArr可以容纳String[]
  • 子类型数组的实例(Object[]可以容纳String[],因为StringObject的子类型),

但不能坚持

  • 引用数组类型的超类型数组(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 AList<A> elements = new ArrayList<A>();elements.add(new B());elements.add(new B());

现在最常见的类型是B,而不是A,所以toArray()

A[] arr = elements.toArray();

将返回Bnew 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.

正确的做法是:

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

我想在这里添加其他很棒的答案,并解释如何使用Javadocs来回答您的问题。

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

public Object[] toArray()

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

不过,在该方法的正下方是toArray(T[] a)的javadoc。如您所见,此方法返回T[],其中T是您传入的数组的类型。起初,这看起来像您要寻找的,但不清楚您传入数组的确切原因(您是在添加数组还是仅将其用于类型等)。留档清楚地表明传递数组的目的本质上是定义要返回的数组类型(这正是您的用例):

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

返回一个数组,其中包含所有此集合中的元素;返回数组的运行时类型为指定数组的。如果集合适合指定的数组,它将返回其中。否则,分配一个新数组指定数组的运行时类型和此数组的大小集合。如果集合适合指定的数组,并且有空间备用(即数组的元素多于集合),则紧跟在集合末尾的数组中的元素设置为空。这在确定文件的长度时很有用只有当调用者知道集合不包含任何空元素。)

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

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

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

另请注意,阅读此处的Javadocs有助于您了解传入的数组的结构应该是什么。尽管这可能实际上并不重要,但您不应该传入像这样的空数组:

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

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

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

等一下,反射是什么?

第8Java:

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