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

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

如果我定义如下:

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

它的工作原理。我是不是遗漏了什么?


当前回答

我可以看到很多答案都展示了如何解决问题,但只有Stephen的答案试图解释问题为什么会发生,所以我将尝试在这个主题上添加更多的东西。这是一个关于Object[] toArray没有被更改为T[] toArray的可能原因的故事,在那里泛型被引入Java。


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

在Java中,泛型类型只在编译时存在。在运行时,关于泛型类型的信息(如在您的例子中<String>)被删除并替换为对象类型(看一下类型擦除)。这就是为什么在运行时toArray()不知道使用什么类型来创建新数组,所以它使用Object作为最安全的类型,因为每个类都扩展Object,所以它可以安全地存储任何类的实例。

现在的问题是你不能将Object[]的实例转换为String[]。

为什么?看一下这个例子(假设类B扩展了a):

//B extends A
A a = new A();
B b = (B)a;

Although such code will compile, at runtime we will see thrown ClassCastException because instance held by reference a is not actually of type B (or its subtypes). Why is this problem (why this exception needs to be cast)? One of the reasons is that B could have new methods/fields which A doesn't, so it is possible that someone will try to use these new members via b reference even if held instance doesn't have (doesn't support) them. In other words we could end up trying to use data which doesn't exist, which could lead to many problems. So to prevent such situation JVM throws exception, and stop further potentially dangerous code.

你现在可能会问:“那我们为什么不早点停下来呢?”为什么涉及这种类型转换的代码甚至是可编译的?编译器不应该停止它吗?答案是:不,因为编译器不能确定一个引用所持有的实例的实际类型是什么,并且有一个机会,它将持有类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超类型,

这不是数组接口,这是不可能的。问题是对象[]数组旁边的字符串可以存储任何对象(例如整数),所以有可能在一个美好的一天,我们将结束尝试调用方法,如strArray[i].substring(1,3)的Integer实例没有这样的方法。

所以为了确保这种情况永远不会发生,在Java中数组引用只能持有

引用String[] strArr可以保存String[] 子类型数组的实例(对象[]可以保存字符串[],因为字符串是对象的子类型),

但不能坚持

引用数组类型的超类型数组(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();

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


这个问题的最佳解决方案是显式地告诉toArray()应该返回什么类型的数组,方法是将该类型作为方法参数传递,如

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

or

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

其他回答

我可以看到很多答案都展示了如何解决问题,但只有Stephen的答案试图解释问题为什么会发生,所以我将尝试在这个主题上添加更多的东西。这是一个关于Object[] toArray没有被更改为T[] toArray的可能原因的故事,在那里泛型被引入Java。


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

在Java中,泛型类型只在编译时存在。在运行时,关于泛型类型的信息(如在您的例子中<String>)被删除并替换为对象类型(看一下类型擦除)。这就是为什么在运行时toArray()不知道使用什么类型来创建新数组,所以它使用Object作为最安全的类型,因为每个类都扩展Object,所以它可以安全地存储任何类的实例。

现在的问题是你不能将Object[]的实例转换为String[]。

为什么?看一下这个例子(假设类B扩展了a):

//B extends A
A a = new A();
B b = (B)a;

Although such code will compile, at runtime we will see thrown ClassCastException because instance held by reference a is not actually of type B (or its subtypes). Why is this problem (why this exception needs to be cast)? One of the reasons is that B could have new methods/fields which A doesn't, so it is possible that someone will try to use these new members via b reference even if held instance doesn't have (doesn't support) them. In other words we could end up trying to use data which doesn't exist, which could lead to many problems. So to prevent such situation JVM throws exception, and stop further potentially dangerous code.

你现在可能会问:“那我们为什么不早点停下来呢?”为什么涉及这种类型转换的代码甚至是可编译的?编译器不应该停止它吗?答案是:不,因为编译器不能确定一个引用所持有的实例的实际类型是什么,并且有一个机会,它将持有类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超类型,

这不是数组接口,这是不可能的。问题是对象[]数组旁边的字符串可以存储任何对象(例如整数),所以有可能在一个美好的一天,我们将结束尝试调用方法,如strArray[i].substring(1,3)的Integer实例没有这样的方法。

所以为了确保这种情况永远不会发生,在Java中数组引用只能持有

引用String[] strArr可以保存String[] 子类型数组的实例(对象[]可以保存字符串[],因为字符串是对象的子类型),

但不能坚持

引用数组类型的超类型数组(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();

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


这个问题的最佳解决方案是显式地告诉toArray()应该返回什么类型的数组,方法是将该类型作为方法参数传递,如

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

or

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

发生的事情是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。相反,在另一种情况下,数组形参给出数组的基类型。(如果提供的数组足够大,可以容纳列表元素,则使用它。否则,将分配一个相同类型且更大的新数组,并作为结果返回。)


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

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

这显然是毫无意义的,这就是数组类型通常不兼容赋值的原因。

试试这个

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

正确的做法是:

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

我想在这里补充其他精彩的答案,并解释如何使用Javadocs来回答您的问题。

Javadoc for toArray()(无参数)在这里。如你所见,这个方法返回Object[]而不是String[], String[]是你的列表的运行时类型的数组:

public Object[] toArray() Returns an array containing all of the elements in this collection. If the collection makes any guarantees as to what order its elements are returned by its iterator, this method must return the elements in the same order. The returned array will be "safe" in that no references to it are maintained by the collection. (In other words, this method must allocate a new array even if the collection is backed by an Array). The caller is thus free to modify the returned array.

不过,在该方法的正下方是Javadoc for toArray(T[] a)。可以看到,该方法返回T[],其中T是传入的数组的类型。起初,这似乎是您正在寻找的东西,但不清楚为什么要传递一个数组(您是添加到数组中,还是仅用于类型等)。文档清楚地表明,传递数组的目的本质上是定义要返回的数组类型(这正是您的用例):

public <T> T[] toArray(T[] a) Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array. If the collection fits in the specified array, it is returned therein. Otherwise, a new array is allocated with the runtime type of the specified array and the size of this collection. If the collection fits in the specified array with room to spare (i.e., the array has more elements than the collection), the element in the array immediately following the end of the collection is set to null. This is useful in determining the length of the collection only if the caller knows that the collection does not contain any null elements.) If this collection makes any guarantees as to what order its elements are returned by its iterator, this method must return the elements in the same order. This implementation checks if the array is large enough to contain the collection; if not, it allocates a new array of the correct size and type (using reflection). Then, it iterates over the collection, storing each object reference in the next consecutive element of the array, starting with element 0. If the array is larger than the collection, a null is stored in the first location after the end of the collection.

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

还要注意,阅读这里的Javadocs有助于理解传入的数组的结构应该是什么。虽然它实际上可能并不重要,但你不应该像这样传入一个空数组:

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

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

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

嘿,等一下,什么是反射?

像这样使用。

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