ChatGPT解决这个技术问题 Extra ChatGPT

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

这个问题在这里已经有了答案:Converting 'ArrayList to 'String[]' in Java (16 answers) Closed 7 years ago。

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

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

如果我定义如下:

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

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

使用 String [] stockArr = (String[]) stock_list.toArray(new String[0]); 参考 java doc here
@Nishant 你不需要投! String[] stockArr = stock_list.toArray(new String[0]); 就足够了。
made this answer 采用更新的方法,JDK-11toArray(T[]) 引入了一个新的、性能相同且语法与 Stream.toArray 相似的 API。

P
Pshemo

像这样使用。

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

为了解释这里发生了什么,JVM 不知道如何盲目地将 Object[](toArray() 的结果)向下转换为 String[]。为了让它知道你想要的对象类型是什么,你可以将一个类型化的数组传递给 toArray()。类型化数组可以是任意大小(new String[1] 有效),但如果它太小,JVM 会自行调整它的大小。
@dhackner - “...... JVM 不知道如何盲目地将 Object[] 转换为 String[]”。或者更准确地说,不允许这样做。如果它可以这样做,那将违反 Java 类型安全。
使用 stock_list.toArray(stockArr) 而不是 stockArr = stock_list.toArray(stockArr)。请参阅stackoverflow.com/a/9572820/597657
如果您的数组列表包含 double, floats, ints, longs(原始类型),您必须将您的数组/数组列表定义为包含原始类型对象,例如 Double, Float, Integer, Long,这对新手也很有用。然后从那里你的普通数组必须用这种类型定义,例如 Double[] myDoubleArray = list.toArray(new Double[listOfDoubles.size()]);
根据有效 Java 中的 Joshua Bloch 的说法,预分配数组会损害性能。改为提供一个长度为零的数组。 stockList.toArray(new String[0])
s
st0le

尝试这个

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

对象[] obj = stock_list.toArray(); String[] stockArr =new String[obj.length]; for(int i=0;i
对于任何想知道@QuanNguyen 评论中发生了什么的人:他基本上是通过 .toArray() 检索 Object[],然后手动将内容复制到新的 String[],将每个元素转换为 String这是一种不好的方法,您应该将新的 String[] 传递给 .toArray()
您不需要传递 list.size(),这只会创建一个更大的临时数组的开销,该数组将被立即丢弃。我可能只是String[] arr = list.toArray(new String[] {});。由于oneliner答案的简单性,仍然给出+1。
@LeoHolanda,如果参数中提供的数组适合列表,则使用相同的数组,如果大小不适合,它会自行分配一个数组,在您的情况下,您最终会创建一个虚拟数组(大小为 0) .
注意 toArray(new String[0])actually faster 而不是 toArray(new String[list.size()])stackoverflow.com/questions/4042434/…
S
Stephen C

发生的情况是 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<Integer> 转换为 int[] ...这是正确的。它将其转换为 Integer[]
V
Vitalii Fedorenko

Java 8 中的替代方案:

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

仅供参考,IntelliJ IDEA 建议将其更改为 list.toArray(new String[0])。我不知道为什么。
@JinKwon 您项目的目标语言版本是什么?
@whirlwin 8,我相信。
或者只是:list.toArray(String[]::new);
C
Community

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

为什么 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 没有的新方法/字段,因此即使持有的实例没有 (不支持)他们。换句话说,我们最终可能会尝试使用不存在的数据,这可能会导致很多问题。所以为了防止这种情况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[] 数组不会有额外的字段或方法,因为每个数组只支持:

[] 操作员,

长度归档,

从 Object 超类型继承的方法,

所以它是 not 数组接口,这使得它变得不可能。问题是 Strings 旁边的 Object[] 数组可以存储任何对象(例如 Integers),因此我们有可能在美好的一天结束尝试在没有此类方法的 Integer 实例上调用类似 strArray[i].substring(1,3) 的方法。

所以为了确保这种情况永远不会发生,在 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();

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

这都是正确的,但问题的根本原因比泛型类型更深/更老。自引入集合框架以来,toArray 方法的行为就是这样。它比通用早几年。 (你可以说,泛型并没有解决问题......)
@StephenC 没错,我只关注泛型问题,忘记了这个工具并不总是集合框架的一部分。我将尝试重写此答案以包含您的信息,但不能不这样做。如果你有时间随时编辑它。如果您认为我不属于这个问题,也可以随意投反对票:)
R
Rick Hanlon II

正确的方法是:

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

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

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

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

但是,在该方法的正下方,是 the Javadoc 代表 toArray(T[] a)。如您所见,此方法返回一个 T[],其中 T 是您传入的数组的类型。起初这似乎是您要查找的内容,但不清楚您传入数组的确切原因(您是否添加到它,仅将其用于类型等)。文档清楚地表明,传递数组的目的本质上是定义要返回的数组类型(这正是您的用例):

public T[] toArray(T[] a) 返回一个包含此集合中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。如果集合适合指定的数组,则在其中返回。否则,将使用指定数组的运行时类型和此集合的大小分配一个新数组。如果集合适合指定的数组并有剩余空间(即,数组的元素比集合多),则数组中紧跟集合末尾的元素设置为 null。仅当调用者知道集合不包含任何空元素时,这在确定集合的长度时才有用。)如果此集合对其迭代器返回其元素的顺序做出任何保证,则此方法必须返回元素以相同的顺序。此实现检查数组是否足够大以包含集合;如果没有,它会分配一个正确大小和类型的新数组(使用反射)。然后,它遍历集合,将每个对象引用存储在数组的下一个连续元素中,从元素 0 开始。如果数组大于集合,则将 null 存储在集合结束后的第一个位置。

当然,要真正理解这两种方法之间的区别,需要了解泛型(如其他答案中所述)。不过,如果您首先访问 Javadocs,通常会找到您的答案,然后自己看看您还需要学习什么(如果您真的这样做的话)。

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

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

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

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

嘿等一下,反射是什么?


对象[] obj = stock_list.toArray();
@QuanNguyen:你在说什么?你想表达什么观点?
toArray(new String[0]) 更快:shipilev.net/blog/2016/arrays-wisdom-ancients