何时使用通用方法以及何时使用通配符?

我正在阅读有关OracleDocGenericMethod的generics方法。 当它说什么时候使用通配符以及何时使用generics方法的时候,我比较困惑。 从文档引用。

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

我们可以在这里使用generics方法:

 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! } 

[…]这告诉我们,types参数被用于多态; 它唯一的作用是允许在不同的调用位置使用各种实际的参数types。 如果是这样的话,应该使用通配符。 通配符被devise为支持灵活的子types,这正是我们要在这里expression的。

我们不认为通配符像(Collection<? extends E> c); 也是支持多态的那种? 那么为什么通用方法的使用被认为是不好呢?

继续前进,它指出,

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

这是什么意思?

他们提出了这个例子

 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) { ... } 

该文件不鼓励第二个声明,并促进使用第一个语法? 第一个和第二个声明有什么区别? 两者似乎都在做同样的事情?

有人可以把光放在这个领域。

有一些地方,通配符和types参数做同样的事情。 但也有一些地方,你必须使用types参数。

  1. 如果你想在不同types的方法参数上强加一些关系,你不能用通配符来实现,你必须使用types参数。

以您的方法为例,假设您要确保传递给copy()方法的srcdest列表应该是相同的参数化types,您可以使用如下所示的types参数:

 public static <T extends Number> void copy(List<T> dest, List<T> src) 

在这里,确保destsrc具有与List相同的参数化types。 所以,将元素从src复制到dest是安全的。

但是,如果你继续改变使用通配符的方法:

 public static void copy(List<? extends Number> dest, List<? extends Number> src) 

它不会按预期工作。 在第二种情况下,可以将List<Integer>List<Float>作为destsrc 。 所以,将元素从src移动到dest将不再是安全的。 如果你不需要这样的关系,那么你完全可以自由地不使用types参数。

使用通配符和types参数之间的一些其他区别是:

  • 如果只有一个参数化types参数,那么可以使用通配符,尽pipetypes参数也可以。
  • types参数支持多个边界,通配符不支持。
  • 通配符支持上下限,types参数只支持上限。 所以,如果你想定义一个采用Integertypes的List或超类的方法,你可以这样做:

     public void print(List<? super Integer> list) // OK 

    但是你不能使用types参数:

      public <T super Integer> void print(List<T> list) // Won't compile 

参考文献:

  • Angelika Langer的Javagenerics常见问题

在你的第一个问题:这意味着如果参数的types和方法的返回types之间有一个关系,那么使用generics。

例如:

 public <T> T giveMeMaximum(Collection<T> items); public <T> Collection<T> applyFilter(Collection<T> items); 

这里您按照一定的标准提取一些T. 如果T是Long你的方法将返回LongCollection<Long> ; 实际返回types取决于参数types,因此使用genericstypes是有用的,并且build议使用。

如果不是这种情况,可以使用通配符types:

 public int count(Collection<?> items); public boolean containsDuplicate(Collection<?> items); 

在这两个例子中,无论集合中哪些types的项目,返回types都是intboolean

在你的例子中:

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

这两个函数将返回一个布尔值,无论是集合中的项目的types。 在第二种情况下,它仅限于E的子类的实例。

第二个问题:

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

这第一个代码允许你传递一个异构的List<? extends T> src List<? extends T> src作为参数。 这个列表可以包含不同类的多个元素,只要它们都扩展基类T.

如果你有:

 interface Fruit{} 

 class Apple implements Fruit{} class Pear implements Fruit{} class Tomato implements Fruit{} 

你可以做

 List<? extends Fruit> basket = new ArrayList<? extends Fruit>(); basket.add(new Apple()); basket.add(new Pear()); basket.add(new Tomato()); List<Fruit> fridge = new ArrayList<Fruit>(); Collections.copy(fridge, basket);// works 

另一方面

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

约束List<S> src属于一个特定的类S,它是T的一个子类。列表只能包含一个类的元素(在这个实例中是S),没有其他类,即使它们也实现了T。 你将无法使用我以前的例子,但你可以这样做:

 List<Apple> basket = new ArrayList<Apple>(); basket.add(new Apple()); basket.add(new Apple()); basket.add(new Apple()); List<Fruit> fridge = new ArrayList<Fruit>(); Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */ 

通配符方法也是通用的 – 你可以用一些范围的types来调用它。

<T>语法定义了一个typesvariables名称。 如果一个typesvariables有任何用法(例如在方法实现中或作为其他types的约束),那么命名它是有意义的,否则你可以使用? ,作为匿名variables。 所以,看起来只是一个捷径。

而且, 当声明一个字段时,语法是不可避免的:

 class NumberContainer { Set<? extends Number> numbers; } 

我会一一试着回答你的问题。

我们不认为通配符像(Collection<? extends E> c); 也是支持多态的那种?

不。原因是有界通配符没有定义的参数types。 这是一个未知数。 所有它“知道”的是,“遏制”是一个typesE (无论定义)。 所以,它不能validation提供的值是否与有界types匹配。

所以,在通配符上使用多态行为是不明智的。

该文件不鼓励第二个声明,并促进使用第一个语法? 第一个和第二个声明有什么区别? 两者似乎都在做同样的事情?

在这种情况下,第一种select是更好的,因为T总是有界的,并且source将肯定具有子类T值(未知数)。

所以,假设你想复制所有的数字列表,第一个选项将会是

 Collections.copy(List<Number> dest, List<? extends Number> src); 

src本质上可以接受List<Double>List<Float>等,因为dest存在参数化types的上限。

第二个选项将强制你为每个要复制的types绑定S ,像这样

 //For double Collections.copy(List<Number> dest, List<Double> src); //Double extends Number. //For int Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number. 

由于S是需要绑定的参数化types。

我希望这有帮助。

请考虑以下来自James Gosling第四版Java编程的例子,下面我们要合并2 SinglyLinkQueue:

 public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){ // merge s element into d } public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){ // merge s element into d } 

上述两种方法都具有相同的function。 那么哪个更好? 答案是第二个。 用作者自己的话说:

“一般规则是尽可能使用通配符,因为带有通配符的代码通常比具有多个types参数的代码更具可读性。当决定是否需要typesvariables时,问问自己是否使用该typesvariables关联两个或多个参数,或者将参数types与返回types联系起来,如果答案是否定的,那么通配符就足够了。

注意:在书中只给出了第二种方法,types参数名称是S而不是“T”。 书中没有第一种方法。

另一个区别是这里没有列出。

 static <T> void fromArrayToCollection(T[] a, Collection<T> c) { for (T o : a) { c.add(o); // correct } } 

但以下将导致编译时错误。

 static <T> void fromArrayToCollection(T[] a, Collection<?> c) { for (T o : a) { c.add(o); // compile time error } }