【问题标题】:Array of specific class to Collection objects?Collection 对象的特定类的数组?
【发布时间】:2011-12-09 20:20:21
【问题描述】:

也许我忽略了一些非常简单和明显的东西......

我有一个类似的方法接口

private void render(Collection<Object> rows);

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

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

当然这样不行,但是为什么不行:

widget.render(Arrays.asList(mods))

它将我的数组变成Module的集合,Module是一个对象...

【问题讨论】:

  • 要使 Collection 的枚举使用 EnumSet.allOf(Module.class) 代替。
  • 如果你有解决问题的办法,你可能应该发布一个答案而不是评论,这样你就可以真正获得投票。然而,其他答案给出了更简单的解决方案。

标签: java arrays generics collections


【解决方案1】:

尝试将您的方法签名更改为:

private void render(Collection<?> rows);

这是说您的方法采用带有任何类型元素的Collection,而之前它说Collection 应该专门将Object 作为其类型参数。

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

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

【讨论】:

  • 啊我明白了...所以这意味着我还没有学到重要的一课:在泛型中没有隐式继承,即 Collection 不是 Collection 的子类型...谢谢
  • java中的所有对象默认扩展对象,这就是为什么任何东西都可以传递给需要对象参数的方法。因此,对象本身并不是将对象传递给它的真正问题,但是如果 Object 对象没有转换为它们的正确类,那么它们的任何特殊方法和变量以及其他属性都将不可访问。
  • @fablife - 没错。请注意,这与数组不同,其中 Module[] 确实继承自 Object[]
【解决方案2】:

因为Collection&lt;Object&gt; 不是Collection&lt;Module&gt;。一个很好的关于泛型的教程是available in PDF version,当你使用泛型时,它是必读的。

这种特殊情况,例如在第 4 页的 Generics and Subtyping 部分中进行了说明。

【讨论】:

  • +1 用于指出要在文档中阅读的内容。第一次阅读时,那部分是真实的“当然,为什么我没有想到这一点”。
  • 如果他只是将每个项目从对象投射到渲染主体内的模块,他的方式会完全正常
  • 我接受了 Khan 的回复,因为他是第一个回答了一个非常简单易懂的解决方案的人,但 +1 指向我指向详细解释的文档。
【解决方案3】:

如果您将集合中的每个对象都转换为模块,例如:

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

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

【讨论】:

    【解决方案4】:

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

    数组示例

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

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

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

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

    最后一行可以正常编译,但如果你运行这段代码,你会得到一个ArrayStoreException

    这意味着你可以欺骗编译器,但不能欺骗运行时类型系统。之所以如此,是因为数组就是我们所说的reifiable types。这意味着在运行时 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 的设计者确保您不能欺骗编译器。如果你不能欺骗编译器(就像我们可以用数组做的那样),你也不能欺骗运行时类型系统。

    因此,我们说泛型类型是不可具体化的

    显然,这会妨碍多态性,正如所指出的那样。解决方案是学习使用 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);
    

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

    但是,假设你会得到一个数字,你就不能安全地从这个逆变结构中读取任何内容。

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

    如您所见,如果编译器允许您编写此行,您将在运行时收到 ClassCastException。

    Get/Put 原则

    因此,当您只打算将泛型值从结构中取出时使用协变,当您只打算将泛型值放入结构中时使用逆变,当您打算同时执行这两种操作时使用精确的泛型类型。

    最好的例子是下面的例子,它将任何类型的数字从一个列表复制到另一个列表。

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

    【讨论】:

      猜你喜欢
      • 2011-09-25
      • 2013-10-06
      • 1970-01-01
      • 1970-01-01
      • 2014-08-29
      • 2017-12-26
      • 2013-09-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多