将ArrayList <String>转换为String array
我正在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()
创build一个Object[]
而不是一个String[]
,因此该types转换失败1 。
正确的代码是:
String [] stockArr = stockList.toArray(new String[stockList.size()]);
甚至
String [] stockArr = stockList.toArray(new String[0]);
有关更多详细信息,请参阅List.toArray
的两个重载的List.toArray
。
(从技术angular度来看,此API行为/devise的原因是List<T>.toArray()
方法的实现没有关于<T>
在运行时的信息,只知道原始元素types是Object
,而在另一种情况下,数组参数给出了数组的基types(如果提供的数组足够大,则使用该数组,否则将使用相同types和更大尺寸的新数组作为结果分配并返回。)
1 – 在Java中, Object[]
与String[]
不是兼容的。 如果是这样,那么你可以这样做:
Object[] objects = new Object[]{new Integer(42)}; String[] strings = (String[]) objects; String s = strings[0]; // Huh???
这显然是无稽之谈,这就是为什么数组types通常不兼容分配。
Java 8中的另一种select:
String[] strings = list.stream().toArray(String[]::new);
我可以看到许多答案显示如何解决问题,但只有斯蒂芬的答案是试图解释为什么问题发生,所以我会尝试添加更多的东西在这个问题上。 这是一个关于为什么Object[] toArray
没有改变为T[] toArray
地方的一个故事,其中generics被引入到Java中。
为什么String[] stockArr = (String[]) stock_list.toArray();
不会工作?
在Java中, genericstypes仅在编译时存在 。 在运行时,关于genericstypes的信息(如你的情况<String>
)将被删除,并replace为Object
types(看看types擦除 )。 这就是为什么在运行时toArray()
不知道用什么精确的types来创build新的数组,所以它使用Object
作为最安全的types,因为每个类都扩展了Object,所以它可以安全地存储任何类的实例。
现在的问题是,你不能将Object[]
实例转换为String[]
。
为什么? 看看这个例子(让我们假设class B extends A
):
//B extends A A a = new A(); B b = (B)a;
尽pipe这样的代码会被编译,但是在运行时我们会看到抛出ClassCastException
因为由引用a
保存的实例实际上并不是B
types(或其子types)。 为什么这个问题(为什么这个exception需要被施放)? 其中一个原因是B
可能有A
没有的新方法/字段,所以有可能有人试图通过b
引用使用这些新成员,即使持有的实例没有(不支持)它们。 换句话说,我们最终可能会尝试使用不存在的数据,这可能会导致很多问题。 所以为了防止这种情况JVM抛出exception,并停止进一步潜在的危险代码。
你现在可以问“为什么我们甚至不能更早地停止?为什么涉及这样的转换的代码甚至是可编译的,不应该被编译器阻止呢?” 答案是:否,因为编译器无法确定引用所持有的实例的实际types是什么,并且有可能存在将支持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[]
数组的实例转换为更精确的typesString[]
Object[] arr = new Object[] { "ab", "cd" }; String[] arr2 = (String[]) arr;//ClassCastException will be thrown
这里的问题有点不同。 现在我们确信String[]
数组不会有额外的字段或方法,因为每个数组只支持:
-
[]
运营商, -
length
提交, - 从Object超typesinheritance的方法,
所以它不是数组接口,这是不可能的。 问题是Strings
旁边的Object[]
数组可能会存储任何对象 (例如Integers
),所以有可能一个美好的一天,我们将最终试图调用strArray[i].substring(1,3)
没有这样的方法的Integer
。
所以为了确保这种情况不会发生,Java数组引用只能保持
- 与引用相同types的数组的实例(引用
String[] strArr
可以包含String[]
) - 子types数组的实例(
Object[]
可以包含String[]
因为String
是Object
子types),
但不能保持
- 来自reference(
String[]
的数组types的超types数组不能包含Object[]
) - 与引用types无关的types数组(
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
你可以说解决这个问题的一种方法是在运行时find所有列表元素之间的最常见的types,并创build该types的数组,但是在列表的所有元素都是从通用types派生的一种types的情况下,这种方式不起作用。 看一看
//B extends A List<A> elements = new ArrayList<A>(); elements.add(new B()); elements.add(new B());
现在最常见的types是B
,而不是A
所以toArray()
A[] arr = elements.toArray();
会返回B
类new B[]
数组。 这个数组的问题在于编译器允许你通过添加new A()
元素来编辑它的内容,但是你会得到ArrayStoreException
因为B[]
数组只能保存B
类或其子类的元素,以确保所有元素将支持B
接口,但A
实例可能没有B
所有方法/字段。 所以这个解决scheme并不完美。
这个问题的最佳解决scheme是通过传递这个types作为方法参数来明确地告诉应该返回什么types的数组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[]
,它是你列表的运行时types的数组:
public Object[] toArray()
返回包含此集合中所有元素的数组。 如果集合对其迭代器返回的元素的顺序做出任何保证,则此方法必须以相同的顺序返回元素。 返回的数组将是“安全”的,因为集合不维护对它的引用。 (换句话说,即使集合由Array支持,这个方法也必须分配一个新的数组)。 调用者可以自由修改返回的数组。
然而,在该方法的下面,是toArray(T[] a)
的Javadoc 。 正如你所看到的,这个方法返回一个T[]
,其中T
是你传入的数组的types。起初,这看起来像你在找什么,但不清楚你为什么传递一个数组你添加到它,使用它只是types等)。 该文档清楚地表明,传递数组的目的主要是定义要返回的数组的types(这正是您的用例):
public <T> T[] toArray(T[] a)
返回包含此集合中所有元素的数组; 返回数组的运行时types是指定数组的运行时types。 如果集合符合指定的数组,则返回其中。 否则,将使用指定数组的运行时types和此集合的大小分配一个新数组。 如果集合符合指定的数组,并且有空余空间(即,数组的元素多于集合),则紧跟在集合结束之后的数组中的元素将设置为null。 只有在调用者知道集合不包含任何空元素的情况下,这对于确定集合的长度才是有用的。)
如果这个集合保证了它的迭代器返回的元素的顺序,这个方法必须以相同的顺序返回元素。
这个实现检查数组是否足够大以包含集合; 如果不是,则分配一个正确大小和types的新数组(使用reflection)。 然后,它遍历集合,将每个对象引用存储在数组的下一个连续元素中,从元素0开始。如果数组大于集合,则在集合结束后将第一个位置存储为null。
当然,对generics的理解(如其他答案所述)需要真正理解这两种方法之间的区别。 不过,如果你第一次去Javadocs,你通常会find你的答案,然后亲自看看你还需要学习什么(如果你真的这么做)。
还要注意,在这里阅读Javadocs可以帮助你理解你传入的数组结构是什么。 虽然实际上可能并不重要,但不应该像这样传递一个空的数组:
String [] stockArr = stockList.toArray(new String[0]);
因为从doc中,这个实现检查数组是否足够大以包含集合; 如果不是,则分配一个正确大小和types的新数组(使用reflection)。 当你可以轻松传递大小时,不需要额外的开销来创build新的数组。
通常情况下,Javadocs为您提供了丰富的信息和方向。
嘿等一下,是什么反映?