Collection对象的特定类数组?

时间:2011-12-09 20:20:21

标签: java arrays generics collections

也许我忽视了一件非常容易和明显的事情......

我有一个类似

的方法界面
private void render(Collection<Object> rows);

现在,我需要传递的对象是一个数组(来自枚举):

Module[] mods = Module.values(); 
widget.render(mods);

当然这不起作用,但为什么这不起作用:

widget.render(Arrays.asList(mods))

它将我的数组转换为Module的集合,而Module是一个对象......

4 个答案:

答案 0 :(得分:6)

尝试将方法签名更改为:

private void render(Collection<?> rows);

这就是说你的方法对Collection采用任何类型的元素,而在它之前说Collection应该特别指定Object作为其类型参数。

使用这样的通配符将限制如何使用传递给方法的Collection,尤其是在修改它时。如果您需要更详细的建议,您可能希望向我们展示您的render方法正在做什么。

关于在Java中使用通配符集合,这篇文章值得一读:What is PECS (Producer Extends Consumer Super)?

答案 1 :(得分:4)

由于Collection<Object>不是Collection<Module>。关于泛型的一个很好的教程是available in PDF version,在使用泛型时必须阅读。

此特定情况,例如,如第4页所述,在Generics and Subtyping部分。

答案 2 :(得分:0)

如果您将Collection中的每个Object转换为Module,例如:

if(object instanceof Module)
{
    Module m = (Module)object;
    //Do stuff here with m
}

然后它应该也可以。否则其他两个答案也可以正常工作。

答案 3 :(得分:0)

其原因基于Java如何实现泛型。我发现解释它的最好方法是精确比较数组和泛型集合。

数组示例

使用数组,您可以这样做:

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

但是,如果你试图这样做会发生什么?

Number[0] = 3.14; //attempt of heap pollution

这最后一行编译得很好,但是如果你运行这段代码,你可以获得ArrayStoreException

这意味着你可以欺骗编译器,但你不能欺骗运行时类型系统。这是因为数组就是我们所说的可再生类型。这意味着在运行时Java知道这个数组实际上是实例化为一个整数数组,它恰好通过Number[]类型的引用访问。

因此,正如您所看到的,有一件事是对象的真实类型,另一件事是您用来访问它的引用类型,对吗?

Java泛型问题

现在,Java泛型类型的问题是编译器丢弃了类型信息,并且在运行时它不可用。此过程称为type erasure。有很好的理由在Java中实现这样的泛型,但这是一个很长的故事,它与二进制兼容现有代码有关。

但重要的是,由于在运行时没有类型信息,因此无法确保我们不会造成堆污染。

例如,

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);

List<Number> myNums = myInts;
myNums.add(3.14); //heap polution

如果Java编译器没有阻止你在编译时这样做,那么运行时类型系统也不能阻止你,因为在运行时没有办法确定这个列表应该只是一个整数列表。 Java运行时允许你将任何你想要的东西放入这个列表,当它只包含整数时,因为它在创建时被声明为整数列表。

因此,Java的设计者确保你不能欺骗编译器。如果你不能欺骗编译器(我们可以用数组做),你也不能欺骗运行时类型系统。

因此,我们说泛型类型不可恢复

显然,这也会妨碍pollymorphism。解决方案是学习使用Java泛型的两个强大功能,称为协方差和逆变。

<强>协方差

使用协方差,您可以从结构中读取项目,但不能在其中写入任何内容。所有这些都是有效的声明。

List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()

您可以阅读myNums

Number n = myNums.get(0);

因为您可以确定无论实际列表包含什么,它都可以被上传到一个数字(毕竟任何扩展Number的数字都是数字,对吗?)

但是,不允许将任何内容放入协变结构中。

myNumst.add(45L);

这是不允许的,因为Java无法保证真实对象的实际类型是什么。它可以是扩展Number的任何东西,但编译器无法确定。所以你可以阅读,但不能写。

<强>逆变

有了逆转,你可以做相反的事情。你可以把东西放到一个通用的结构中,但你不能从中读出来。

List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");

List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

在这种情况下,对象的实际性质是对象列表,并且通过逆变,您可以将Numbers放入其中,主要是因为数字将Object作为共同的祖先。因此,所有Numbers都是对象,因此这是有效的。

然而,假设您将得到一个数字,您无法安全地从这个逆变结构中读取任何内容。

Number myNum = myNums.get(0); //compiler-error

如您所见,如果编译器允许您编写此行,则会在运行时获得ClassCastException。

获取/放置原则

因此,当您只打算从结构中取出通用值时使用协方差,当您只打算将通用值放入结构时使用逆变,并在打算同时使用完全通用类型时使用。[/ p >

我所拥有的最好的例子是将任何类型的数字从一个列表复制到另一个列表中。

public static void copy(List<? extends Number> source, List<? super Number> destiny) {
    for(Number number : source) {
        destiny.add(number);
    }
}

由于协方差和逆变的力量,这适用于这样的情况:

List<Integer> myInts = asList(1,2,3,4);
List<Integer> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

copy(myInts, myObjs);
copy(myDoubles, myObjs);