简体   繁体   English

Java泛型捕获列表<?>

[英]Java Generics Capture List<?>

I was looking at the Java Generics documentation and found this piece of code, 我正在查看Java Generics文档并找到这段代码,

public class WildcardError {

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

I can understand that we are fetching an element from a List<?> and trying to set it to another List<?> . 我可以理解我们从List<?>中获取一个元素并尝试将其设置为另一个List<?> So the compiler gives an error. 所以编译器会出错。 My question is it makes sense when the 2 lists are different ie l.set(0, m.get(0)) here lists l and m are different. 我的问题是当2个列表不同l.set(0, m.get(0))l.set(0, m.get(0))这里列出lm是不同的。 But in the above example, l and l are the same lists. 但在上面的例子中, ll是相同的列表。 Why isn't the compiler smart enough to see that? 为什么编译器不够聪明才能看到它? Is it hard to implement it? 实施起来难吗?

Edit : I am aware that I can fix it by a helper method or by using T instead of a ? 编辑 :我知道我可以通过辅助方法或使用T而不是?来修复它? . Just wondering why compiler doesn't do it for me. 只是想知道为什么编译器不会为我做这件事。

In your specific case, you can explicitly fix this: 在您的具体情况下,您可以明确解决此问题:

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

Or if you don't want to change the original API, introduce a delegate helper method: 或者,如果您不想更改原始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));
    }
}

Unfortunately, the compiler cannot infer that "obvious" <T> type. 不幸的是,编译器无法推断出“明显的” <T>类型。 I've been wondering about that, too. 我一直在想这个。 It seems like something that could be improved in a compiler, as every wild card can be informally translated to an unknown <T> type. 它似乎可以在编译器中得到改进,因为每个通配符都可以非正式地转换为未知的<T>类型。 Probably, there are some reasons why this was omitted, perhaps this is only intuition, but formally impossible. 可能有一些原因可以解释为什么这个被省略,也许这只是直觉,但在形式上是不可能的。

UPDATE : 更新

Note, I've just seen this peculiar implementation of Collections.swap() : 注意,我刚刚看到了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)));
}

The JDK guys resort to a raw type, in order to handle this, locally. JDK家伙采用原始类型,以便在本地处理此问题。 This is a strong statement indicating that this probably should be supported by the compiler, but for some reason (eg lack of time to formally specify this) just wasn't done 这是一个强有力的声明,表明这可能应该由编译器支持,但由于某种原因(例如,没有时间正式指定这一点)只是没有完成

The compiler reports an error because there is no way -- in general -- that it can tell whether two expressions, (in this case l and l ) refer to the same list. 编译器会报告错误,因为通常情况下它无法判断两个表达式(在本例中为ll )是否引用相同的列表。

Related, somewhat generalized, question: 相关的,有点概括的问题:

List<?> means list containing elements of some unknown type , so when one wants to take elements from it using list.get(i) it will return object of some unknown type , so the only valid guess will be Object . List<?>表示包含某些未知类型的元素的列表 ,因此当想要使用list.get(i)获取元素时,它将返回某种未知类型的对象 ,因此唯一有效的猜测将是Object Then when one tries to set element back using list.set(index, list.get(index)) it produces compile-time error, since as mentioned above List<?> can only contain some unknown type , so putting Object to it may cause ClassCastException . 然后当使用list.set(index, list.get(index))尝试设置元素时list.set(index, list.get(index))它会产生编译时错误,因为如上所述, List<?>只能包含一些未知类型 ,因此将Object放入其中可能导致ClassCastException

This is explained very well in Joshua Bloch's Effective Java, 2nd ed., Item 28: Use bounded wildcards to increase API flexibility Joshua Bloch的Effective Java,第2版,第28项:使用有界通配符增加API灵活性,这一点得到了很好的解释

This is also known as PECS principle and good explanation can be found in this Q/A: What is PECS (Producer Extends Consumer Super)? 这也称为PECS原理,在此Q / A中可以找到很好的解释: 什么是PECS(Producer扩展消费者超级)? (please note that List<?> is the same as List<? extends Object> with minor exceptions) (请注意, List<?>List<? extends Object>相同,但有少量例外)

In laymans terms, one should use List<?> as method parameter only to get elements from it inside that method, not when one needs to put elements into list. 在非专业术语中,应该使用List<?>作为方法参数,仅在该方法中从中获取元素 ,而不是在需要将元素放入列表时。 When one needs to both put and get he/she needs to either generify method using type parameter T as in Lukas Eder's answer (type-safe way) or simply use List<Object> (not type-safe way). 当需要同时放置和获取他/她需要使用类型参数T生成方法时,如Lukas Eder的答案(类型安全方式)或简单地使用List<Object> (不是类型安全方式)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM