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

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

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

如果我定义如下:

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

有用。有什么我想念的吗?

答案

这样使用。

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

尝试这个

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

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

后者使用零长度数组来确定结果数组的类型。 (令人惊讶的是,至少在最近的 Java 版本中,这样做比预分配要快得多。有关详细信息,请参阅https://stackoverflow.com/a/4042464/139985 。)

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


1 - 在 Java 中, Object[]String[]不兼容。如果是这样,则可以执行以下操作:

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

这显然是胡说八道,这就是为什么数组类型通常不兼容分配的原因。

Java 8 中的替代方法:

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

我可以看到许多显示如何解决问题的答案 ,但是只有Stephen 的答案试图解释为什么会出现问题,因此我将尝试在此主题上添加更多内容。这是一个有关为什么将Object[] toArray不更改为T[] toArray可能原因的故事,在 Java 中引入了泛型商品。


为什么String[] stockArr = (String[]) stock_list.toArray();不会工作吗?

在 Java 中, 泛型类型仅在编译时存在 。在运行时,有关泛型类型(例如您的<String> )的信息将被删除,并替换为Object类型(请查看type erasure )。这就是为什么在运行时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
  • 从对象超类型继承的方法,

因此, 不是数组接口是不可能的。问题是Strings旁边的Object[]数组可以存储任何对象 (例如Integers ),因此有一天我们可能会尝试在实例上调用诸如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 A
List<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)

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

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

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

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

还要注意,在这里阅读 Javadocs 可以帮助您了解传入的数组的结构。尽管实际上可能并不重要,但您不应传递这样的空数组:

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

因为此实现从文档中检查数组是否足够大以包含集合;因此,请执行以下操作: 如果不是,它将分配正确大小和类型的新数组(使用反射)。当您可以轻松传递大小时,在创建新数组时不需要额外的开销。

通常,Javadocs 为您提供大量信息和指导。

嘿,等一下,这反映了什么?