猿问

何时使用通用方法,何时使用通配符?

何时使用通用方法,何时使用通配符?

我正在阅读关于泛型方法的文章。OracleDocGenericMethod..当它说明何时使用通配符和何时使用泛型方法时,我对这种比较感到非常困惑。引用文件中的话。

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);}

我们本可以在这里使用通用方法,而不是:

interface Collection<E> {
    public <T> boolean containsAll(Collection<T> c);
    public <T extends E> boolean addAll(Collection<T> c);
    // Hey, type variables can have bounds too!}

[…]这告诉我们,类型参数用于多态性;它的唯一效果是允许在不同的调用站点上使用各种实际的参数类型。如果是这样的话,应该使用通配符。通配符是为了支持灵活的子类型而设计的,这正是我们在这里试图表达的。

我们不觉得不像(Collection<? extends E> c);是否也支持某种多态性?那么,为什么在这种情况下泛型方法的使用被认为是不好的呢?

继续向前,它说,

泛型方法允许使用类型参数来表示方法和/或其返回类型的一个或多个参数类型之间的依赖关系。如果不存在这种依赖关系,则不应该使用泛型方法。

这是什么意思?

他们给出了这个例子

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...}

[…]

我们可以用另一种方式编写此方法的签名,而无需使用通配符:

class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...}

文档阻止第二个声明并促进第一个语法的使用?第一次声明和第二次声明有什么区别?两人似乎都在做同样的事?

有人能给这个地区开个灯吗。


潇湘沐
浏览 493回答 3
3回答

HUWWW

在某些地方,通配符和类型参数会做同样的事情。但也有一些地方,您必须使用类型参数。如果要对不同类型的方法参数强制执行某种关系,则不能使用通配符,必须使用类型参数。以您的方法为例,假设您希望确保src和dest传递给copy()方法应该是相同的参数化类型,您可以使用类型参数这样做:public&nbsp;static&nbsp;<T&nbsp;extends&nbsp;Number>&nbsp;void&nbsp;copy(List<T>&nbsp;dest,&nbsp;List<T>&nbsp;src)在这里,你可以确保dest和src具有相同的参数化类型List..所以,可以安全地从src到dest.但是,如果您继续更改方法以使用通配符:public&nbsp;static&nbsp;void&nbsp;copy(List<?&nbsp;extends&nbsp;Number>&nbsp;dest,&nbsp;List<?&nbsp;extends&nbsp;Number>&nbsp;src)它不会像预期的那样起作用。在第二种情况下,你可以通过List<Integer>和List<Float>如dest和src..因此,将元素从src到dest不会再安全了。如果您不需要这样的关系,那么您可以完全不使用类型参数。使用通配符和类型参数之间的其他区别是:如果只有一个参数化类型参数,则可以使用通配符,尽管类型参数也会工作。类型参数支持多个边界,通配符不支持。通配符支持上下界,类型参数只支持上界。因此,如果您想要定义一个采用List类型Integer或者是超级班,你可以:public&nbsp;void&nbsp;print(List<?&nbsp;super&nbsp;Integer>&nbsp;list)&nbsp;&nbsp;//&nbsp;OK但是不能使用类型参数:&nbsp;public&nbsp;<T&nbsp;super&nbsp;Integer>&nbsp;void&nbsp;print(List<T>&nbsp;list)&nbsp;&nbsp;//&nbsp;Won't&nbsp;compile参考资料:Angelika Langer的Java泛型常见问题

四季花海

考虑以下由JamesGosling第4版编写的Java编程示例,在下面我们要合并2 SinglyLinkQueue:public&nbsp;static&nbsp;<T1,&nbsp;T2&nbsp;extends&nbsp;T1>&nbsp;void&nbsp;merge(SinglyLinkQueue<T1>&nbsp;d,&nbsp;SinglyLinkQueue<T2>&nbsp;s){ &nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;merge&nbsp;s&nbsp;element&nbsp;into&nbsp;d}public&nbsp;static&nbsp;<T>&nbsp;void&nbsp;merge(SinglyLinkQueue<T>&nbsp;d,&nbsp;SinglyLinkQueue<?&nbsp;extends&nbsp;T>&nbsp;s){ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;merge&nbsp;s&nbsp;element&nbsp;into&nbsp;d}上述两种方法都具有相同的功能。那么哪个更好呢?答案是第二。用作者自己的话说:“一般规则是在可能时使用通配符,因为通配符代码通常比具有多个类型参数的代码更具可读性。在决定是否需要类型变量时,问问自己该类型变量是否用于关联两个或多个参数,还是将参数类型与返回类型相关联。如果答案是否定的,那么通配符就足够了。”注意:书中只给出了第二种方法,类型参数名为S而不是‘T’。第一种方法不在书中。

冉冉说

在第一个问题中:这意味着如果参数的类型与方法的返回类型之间存在关系,那么使用泛型。例如:public&nbsp;<T>&nbsp;T&nbsp;giveMeMaximum(Collection<T>&nbsp;items);public&nbsp;<T>&nbsp;Collection<T>&nbsp;applyFilter(Collection<T>&nbsp;items);在这里,您将按照一定的标准提取一些T。如果T是Long您的方法将返回Long和Collection<Long>实际返回类型取决于参数类型,因此使用泛型类型是有用的,并建议使用泛型类型。如果不是这种情况,可以使用通配符类型:public&nbsp;int&nbsp;count(Collection<?>&nbsp;items);public&nbsp;boolean&nbsp;containsDuplicate(Collection<?>&nbsp;items);在这两个示例中,无论集合中的项的类型是什么,返回类型都是int和boolean.在你们的例子中:interface&nbsp;Collection<E>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;boolean&nbsp;containsAll(Collection<?>&nbsp;c); &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;boolean&nbsp;addAll(Collection<?&nbsp;extends&nbsp;E>&nbsp;c);}这两个函数将返回一个布尔值,不管集合中项的类型如何。在第二种情况下,它仅限于E.第二个问题:class&nbsp;Collections&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;<T>&nbsp;void&nbsp;copy(List<T>&nbsp;dest,&nbsp;List<?&nbsp;extends&nbsp;T>&nbsp;src)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;...}这第一段代码允许您传递一个异构代码。List<? extends T> src作为参数。这个列表可以包含不同类的多个元素,只要它们都扩展了基类T。如果你有:interface&nbsp;Fruit{}和class&nbsp;Apple&nbsp;implements&nbsp;Fruit{}class&nbsp;Pear&nbsp;implements&nbsp;Fruit{}class&nbsp;Tomato&nbsp;implements&nbsp;Fruit{}你可以List<?&nbsp;extends&nbsp;Fruit>&nbsp;basket&nbsp;=&nbsp;new&nbsp;ArrayList<?&nbsp;extends&nbsp;Fruit>();basket.add(new&nbsp;Apple());basket.add(new&nbsp;Pear()); basket.add(new&nbsp;Tomato());List<Fruit>&nbsp;fridge&nbsp;=&nbsp;new&nbsp;ArrayList<Fruit>();&nbsp;Collections.copy(fridge,&nbsp;basket);//&nbsp;works另一方面class&nbsp;Collections&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;<T,&nbsp;S&nbsp;extends&nbsp;T>&nbsp;void&nbsp;copy(List<T>&nbsp;dest,&nbsp;List<S>&nbsp;src)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;...}约束List<S> src作为一个特殊的类S,它是T的一个子类,列表只能包含一个类的元素(在本例中是S),而不能包含其他类的元素,即使它们也实现了T。您将无法使用我前面的示例,但您可以这样做:List<Apple>&nbsp;basket&nbsp;=&nbsp;new&nbsp;ArrayList<Apple>();basket.add(new&nbsp;Apple());basket.add(new&nbsp;Apple());basket.add(new&nbsp;Apple()); List<Fruit>&nbsp;fridge&nbsp;=&nbsp;new&nbsp;ArrayList<Fruit>();Collections.copy(fridge,&nbsp;basket); /*&nbsp;works&nbsp;since&nbsp;the&nbsp;basket&nbsp;is&nbsp;defined&nbsp;as&nbsp;a&nbsp;List&nbsp;of&nbsp;apples&nbsp;and&nbsp;not&nbsp;a&nbsp;list&nbsp;of&nbsp;some&nbsp;fruits.&nbsp;*/
随时随地看视频慕课网APP

相关分类

Java
我要回答