我有一个使用varargs功能的方法:
void add(Animal ...);
现在,我没有做.add(dog, cat)
,而是有一个动物列表,其中包含未知数量的元素,
List<Animal> i = new ArrayList<Animal>();
i.add(dog);
i.add(cat);
并希望使用此列表的元素调用add。
我想我可以使用一个数组,但是当我执行.add(i.toArray())
时,它会产生编译错误。
这样做的正确方法是什么?
答案 0 :(得分:11)
这是:
add(i.toArray(new Animal[i.size()]))
无论列表中的类型参数如何, List.toArray都会返回Object[]
:即使您可能会写new List<String>().toArray()
,也会得到Object[]
。但是,version of toArray that takes an array to fill会返回一个类型正确的数组:如果您编写new List<String>().toArray(new String[0])
,则会得到String[]
。请注意,传入的数组的大小甚至不必与列表的大小相匹配,尽管确保它的确是这样。但
这最终归因于泛型的一个稍微棘手的特征。乍一看,您可能认为String[]
和List<String>
对于它们的基本类型意味着类似的东西 - 一个是字符串数组,另一个是字符串列表。
然而,它们实际上是非常不同的。
数组是一种语言基元,并且其类型已烘焙到其中。如果您使用十六进制编辑器并查看JVM内存中的数组实例,您将能够找到(在附近的某个位置)它所拥有的对象类型的记录。这意味着如果您获取某个未知组件类型的数组实例,则可以find out what that type is。相反,这意味着如果你要去create an instance of an array,你需要知道你想要的组件类型。
另一方面,List
使用泛型,在Java中用type erasure实现,这意味着,粗略地说,它是存在于编译器中的东西,但不是在运行时(编译器可以检查你是否正确,但JVM不能)。这导致了一个简单而有效的实现(一个简单到足以在不更改JVM的情况下添加到预泛化Java),但它有一些缺点 - 特别是在运行时,没有办法告诉类型参数是什么在泛型类的任何特定实例上,因为类型参数仅存在于编译器中。因为List
实例需要处理toArray()
,所以它唯一能做的就是创建Object[]
。它只是不知道更具体的类型。
一种看待这种情况的方法是数组的类型参数是类的一部分,而List
的类型参数是类型的一部分,既然对象有类但变量有类型,你不能从一个对象获取List
的类型参数,只能从一个持有一个对象的变量中获取(另外,你也无法获得来自包含数组的变量的数组的类型参数(考虑Object[] array = new String[0];
),但这并不重要,因为变量可以让你获得一个对象 - 除非它是空的。)
要将其归结为代码,问题是:
public <E> E[] createSimilarlyTypedArray(List<E> list) {
Class<E> componentType = list.???; // there is no way to do this
return Arrays.newInstance(componentType, list.size());
}
答案 1 :(得分:0)
当我执行.add(i.toArray())时,它会给出错误,正确的方法是什么 做到了吗?
使用foo.addAll(i)
然后根据需要将foo转换为数组。
答案 2 :(得分:0)
您的方法void add(Animal...)
需要Animal
类的对象或包含Animal对象的数组。你给它一个包含Object
类对象的数组。给List一个类似的通用类型:
List<Animal> animals = new ArrayList<Animal>();
animals.add(dog);
animals.add(cat)
然后将列表解析为参数,同时将其转换为数组,方法如下:
add(animals.toArray(new Animal[animals.size()]);
有关泛型的更多信息,请参阅Java API
http://download.oracle.com/javase/1,5.0/docs/guide/language/generics.html