【问题标题】:Java Generics Capture List<?>Java 泛型捕获列表<?>
【发布时间】:2014-01-28 18:18:51
【问题描述】:

我在看Java Generics documentation,发现了这段代码,

public class WildcardError {

void foo(List<?> l) {
    //This give a compile time error
    l.set(0,l.get(0));
}
}

我可以理解我们正在从 List&lt;?&gt; 获取一个元素并尝试将其设置为另一个 List&lt;?&gt;。所以编译器会报错。我的问题是当两个列表不同时有意义,即l.set(0, m.get(0)) 这里列表lm 不同。但是在上面的例子中,ll 是同一个列表。为什么编译器不够聪明,看不到这一点?实施起来难吗?

编辑: 我知道我可以通过辅助方法或使用T 而不是? 来修复它。只是想知道为什么编译器不为我做。

【问题讨论】:

    标签: java generics


    【解决方案1】:

    在您的具体情况下,您可以明确解决此问题:

    public class WildcardError {
        <T> void foo(List<T> l) {
            // This will work
            l.set(0, l.get(0));
        }
    }
    

    或者如果您不想更改原始 API,请引入委托辅助方法:

    public class WildcardError {
        void foo(List<?> l) {
            foo0(l);
        }
    
        private <T> void foo0(List<T> l) {
            // This will work
            l.set(0, l.get(0));
        }
    }
    

    不幸的是,编译器无法推断出“明显的”&lt;T&gt; 类型。我也一直在想。这似乎是可以在编译器中改进的东西,因为每个通配符都可以非正式地转换为未知的 &lt;T&gt; 类型。可能,这被省略是有一些原因的,也许这只是直觉,但形式上是不可能的。

    更新

    注意,我刚刚看到Collections.swap() 的这种特殊实现:

    public static void swap(List<?> list, int i, int j) {
        final List l = list;
        l.set(i, l.set(j, l.get(i)));
    }
    

    JDK 人员使用原始类型来在本地处理这个问题。这是一个强有力的声明,表明编译器可能应该支持这一点,但由于某种原因(例如,没有时间正式指定这一点)并没有完成

    【讨论】:

    • 是的,我知道解决此问题的其他替代方法。只是想知道为什么当两个列表相同时编译器不能自己做到这一点。
    • 是的,我也想知道这一点。它会让我在过去无法编写数百个无用的委托方法...... :-/ 也许你应该更新你的问题并要求 JLS 提供具体解释,这解释了这一点。
    • 感谢您编辑帖子:)。我在哪里可以询问 JLS?
    • @vikky.rk:我重新表述了你的问题here,以获得更正式的答案。通常,如果您想了解 Java 语言的特性,最好提及 JLS 并要求正式的解释
    • 谢谢。那么对于“JLS”标签,您是否希望 JLS 的任何人回复?抱歉,我是 SO 新手,所以只了解它的工作原理。
    【解决方案2】:

    编译器报告错误是因为它无法(通常)判断两个表达式(在本例中为 ll)是否引用同一个列表。

    相关的,有点笼统的问题:

    【讨论】:

    • 长期以来,我一直在想一个正式的原因。我个人觉得这是 JLS 中缺少的一个功能,因为 l 中的两个通配符在形式上是相同的。至少凭直觉……但可能在更复杂的代码中,识别“相同”的通配符会变得更加困难
    • 是的。可能有一些有趣的极端情况会迫使规范变得相当复杂。不过是个好问题。这是对这个问题的一种概括。您应该将其发布为后续行动。
    • 是的。你发的时候我看到了+1。
    • 检查我更新的答案。我刚刚在 JDK 中发现了一个“遭受”同样问题的方法...... JDK 人使用原始类型来规避这个问题。
    【解决方案3】:

    List&lt;?&gt; 表示列表包含某种未知类型的元素,所以当你想使用list.get(i) 从中获取元素时,它会返回某种未知类型的对象 ,所以唯一有效的猜测是Object。然后当尝试使用list.set(index, list.get(index)) 设置元素时,它会产生编译时错误,因为如上所述List&lt;?&gt; 只能包含一些未知类型,所以将Object 放入它可能会导致@ 987654328@.

    这在 Joshua Bloch 的 Effective Java, 2nd ed., Item 28: Use bounded wildcards to increase API灵活性中得到了很好的解释

    这也称为PECS原理,可以在这个Q/A中找到很好的解释: What is PECS (Producer Extends Consumer Super)?(请注意,List&lt;?&gt;List&lt;? extends Object&gt; 相同,但有小例外)

    通俗地说,应该使用List&lt;?&gt; 作为方法参数,仅用于在该方法中获取元素,而不是在需要将元素 放入列表时。当一个人需要 put 和 get 时,他/她需要使用类型参数 T 来泛化方法,如 Lukas Eder 的回答(类型安全的方式)或简单地使用 List&lt;Object&gt;(不是类型 -安全的方式)。

    【讨论】:

      猜你喜欢
      • 2017-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多