2015-04-24 34 views
2

我正在学习Java泛型,我正在阅读Naftalin和Wadler编写的(非常好的)一本书,并且他谈到了捕获通配符的通配符,比如实现在Collections.reverse:Java泛型在通用方法中捕获通配符

public static <T> void reverse(List<T> list){ 
    List<T> temp=new ArrayList<>(list); 
    for(int i=0;i<list.size();i++) 
     list.set(i,temp.get(list.size()-1-i)); 
} 

他说,在集合类中的方法是使用通配符简单实现:

public static void reverse(List<?> list){ 
    //code.. 
} 

,但使用第一种方法体是行不通的:

public static void reverse(List<?> list){ 
    List<Object> temp=new ArrayList<Object>(list); //legal 
    for(int i=0;i<list.size();i++) 
     list.set(temp.get(list.size()-1-i));  //illegal 
} 

这是行不通的,因为它试图把一个对象类型元素的列表 类型未知(?),它是一切扩展对象(这是..well,一切)

因此调用从第二第一种方法应该做的伎俩:

public static void reverse1(List<?> list){ 
    reverse2(list); 
} 

public static <T> void reverse2(List<T> list){ 
    List<T> temp=new ArrayList<T>(list); 
    for(int i=0;i<list.size();i++) 
     list.set(i,temp.get(list.size()-1-i)); 
} 

至此,继在方法调用发生的事情,例如传递

List<String> myList 

1)List<String> myList被上浇铸到本地变量String<?> list(字符串延伸对象,这是上限通配符的,使得List<String>亚型List<?>

2)list现在传递给reverse2()和参数T是推断为? extends Object,现在我怎么可以使用这个作为参数,当我实例化新ArrayList<T>() ???这在Java代码中显然是非法的,所以其他事情必须发生,请问你是什么?

感谢

卢卡

+0

我没有看到这里的任何东西正在实例化'ArrayList '。请围绕代码片段(以及任何需要'<' and '>'被正确渲染的代码文本)用反引号(又名反引号'''')。或者您可以突出显示文本并按下“{}”按钮。 – rgettman

+0

@rgettman完成。我通过类型参数,因为我调用的方法..我不明白我怎么能写方法体传递?作为类型参数。 – Luca

回答

3

T参数在reverse2()推断为? extends Object,并使用通配符,?不进行实例化。

推理只会发生在调用reverse2()的方法中。例如,如果您拨打Collections.emptyList(),什么是类型参数?在这个例子中,它是未知的,但它通常可以在调用现场推断:

List<String> empty = Collections.emptyList(); 

被推断为是Collections.<String>emptyList()调用(显式的形式)。

在你的情况下,T没有限制,所以任何类型都是兼容的。但是,如果类型变量被声明为T extends String,则通配符?将过于宽泛以至于不能满足该限制,并且该调用将是非法的。


好吧,我得到它,所以它是什么T接?我的意思是,推断T是什么?

T是在reverse2()类型变量,并且如我如上所述,类型推断发生在呼叫者,而不是被叫方,所以T不是“推断”是任何东西。

也许你的意思是什么类型显示在编译的字节码?在这种情况下,没有声明T类型的变量;永远不会使用T,也不会进行类型检查。因此,考虑以下人为的例子:

final class Reverse { 

    static <T extends String> void reverse(List<T> list) { 
    List<T> tmp = new ArrayList<>(list); 
    for (int i = 0; i < list.size(); ++i) 
     list.set(i, tmp.get(list.size() - 1 - i)); 
    } 

} 

现在,调用该方法的客户端:

final class Test { 

    public static void main(String... argv) { 
    List<String> list = Arrays.asList("A", "B", "C"); 
    Reverse.reverse(list); 
    System.out.println(list); 
    } 

} 

编译这些类一起运行Test,你会得到[C, B, A],符合市场预期。现在,无需重新编译Test改变reverse()方法的签名并重新编译只有Reverse类:

static <T extends Integer> void reverse(List<T> list) 

重新运行Test会产生相同的结果,而不是失败!

现在改变reverse()方法的实现,并再次,重新编译只有Reverse类:

static <T extends Integer> void reverse(List<T> list) { 
    List<T> tmp = new ArrayList<>(list); 
    for (int i = 0; i < list.size(); ++i) { 
    T el = tmp.get(list.size() - 1 - i); 
    list.set(i, el); 
    } 
} 

这一次,运行Test会产生你预期的失败最后一次:

 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 

那是因为T现在被实际参考:

T el = tmp.get(list.size() - 1 - i); 

向该分配编译器将插入铸造到上限类型参数,在这种情况下是Integer的:

T el = (Integer) tmp.get(list.size() - 1 - i); 

如果类型T不受限制(其上限是Object)无演员被执行,因为它永远不会失败。

+0

好的,我知道了,那么T是什么呢?我的意思是,推断T是什么? – Luca

+0

好吧,我了解一些东西,但我不能诚实地得到一切....我无法理解这种泛型的内部工作原理... – Luca

+0

@erickson static void reverse(List list) 这是编译时错误 – shikhar

0

list现在传递给reverse2()和参数T被推断为是? extends Object,[&hellip;]。

reverse2被调用时,通配符是捕获到新鲜的类型的变量,其是不再通配符 *的类型T被推断为这个捕获类型。

类型系统确保我们只能以安全的方式使用此功能。

例如,假设我们有哪些接受列表的方法:

static <T> void swap2(List<T> list1, List<T> list2) { 
    List<T> temp = new ArrayList<>(list1); 
    list1.clear(); 
    list1.addAll(list2); 
    list2.clear(); 
    list2.addAll(temp); 
} 

如果我们试图用通配符来调用这个捕捉:

static void swap(List<?> list1, List<?> list2) { 
    swap2(list1, list2); // <- doesn't compile 
} 

这将无法编译,因为编译器知道它不能推断list1list2在传递到swap之前具有相同的类型。

reverse2是类型安全的,因为它将元素添加到列表中,该元素最初在列表中开始。


*技术证明是5.1.10 Capture Conversion

如果Ti是形式?通配符类型参数,然后Si清爽型可变 [&hellip;]。

+0

我不知道所有这些捕获通配符的东西,我发现很难理解,因为我刚开始学习泛型... 在哪里可以找到关于这个主题容易理解的文档? – Luca

+0

可能[Angelika Langer的常见问题](http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html)。当然[官方教程](https://docs.oracle.com/javase/tutorial/java/generics/index.html)。但是我没有意识到这是全面的,正确的,并且仍然容易理解。 (我认为StackOverflow更多的是这样的方式,但是它的知识存在一些缺陷。)捕获的技术推理实际上非常复杂......我在阅读完JLS后才完全理解它,这非常令人生畏。 – Radiodef

+0

好的,所以我会在稍后的时间把它用在我对语言的知识是有声的时候......现在我只想了解我在做什么来编写一些体面的代码。 谢谢。 – Luca