何时在Java Generics中使用通配符?

这是来自HeadFirst Java :(第575页)

这个:

public  void takeThing(ArrayList list) 

与此相同:

 public void takeThing(ArrayList list) 

所以这是我的问题:如果它们完全相同,我们为什么不写

 public  void takeThing(ArrayList list) 

要么

 public void takeThing(ArrayList list) 

此外,何时使用?有用? 使用generics或类声明而不是方法声明中的T(如上所述)? 有什么好处?

两者之间的巨大差异

 public  void takeThing(ArrayList list) 

 public void takeThing(ArrayList list) 

在前一种方法中,您可以将方法中的“T”称为给定的具体类。 在第二种方法中,你不能这样做。

这里有一个更复杂的例子来说明这一点:

 // here i can return the concrete type that was passed in public  Map getNamesMap(ArrayList list) { Map names = new HashMap(); for (T animal : list) { names.put(animal, animal.getName()); // i assume there is a getName method } return names; } // here i have to use general Animal public Map getNamesMap(ArrayList list) { Map names = new HashMap(); for (Animal animal : list) { names.put(animal, animal.getName()); // i assume there is a getName method } return names; } 

使用第一种方法,如果您传入一个猫列表,您将获得一个以猫为主的地图。 第二种方法总是返回带有常规Animal键的Map。

顺便说一下,这是无效的java语法:

 public  void takeThing(ArrayList list) 

使用这种forms的generics方法声明,您必须使用有效的java标识符而不是“?”。

编辑:

“?extends Type”forms仅适用于变量或参数类型声明。 在通用方法declration中,它必须是“Identifier extends Type”,因为您可以在方法中引用“Identifier”。

通配符是关于generics的共同/反转方差。 我将通过提供一些例子来试图弄清楚这意味着什么。

基本上它与以下事实有关:对于类型S和T,其中S是T的子类型,genericsG不是G的有效子类型。

 List someNumbers = new ArrayList(); // compile error 

你可以用外卡来解决这个问题

 List someNumbers = new ArrayList(); // this works 

请注意,您不能将任何内容放入此类列表中

 someNumbers.add(2L); //compile error 

甚至(对许多开发人员来说更令人惊讶):

 List someLongs = new ArrayList(); someLongs.add(2L); // compile error !!! 

我认为SO不是详细讨论它的正确位置。 我将尝试找到一些更详细解释这一点的文章和论文。

将类型绑定到类型参数可能更强大,具体取决于方法应该执行的操作。 我不确定takeThing应该做什么,但想象一般我们有一个带有这些类型签名之一的方法:

 public  void foo(ArrayList list); //or public void foo(ArrayList list); 

以下是您只能使用第一个类型签名的具体示例:

 public  void foo(ArrayList list) { list.add(list.remove(0)); // (cycle front element to the back) } 

在这种情况下,需要T通知类型检查器从列表中删除的元素是要添加到列表的OK元素。

您不能使用通配符执行此操作,因为由于通配符尚未绑定到类型参数,因此不会跟踪其上下文(嗯,通过“捕获”跟踪它,但它无法利用)。 您可以在我给出的另一个答案中获得有关此内容的更多信息: generics的generics如何工作?

如果你写? extends T ? extends T你说“任何T或更具体的东西”。 例如: List只能包含Shape ,而List List可以有Shape s, Circle s, Rectangle s等。

如果你写? super T ? super T你说“任何T或更普遍的东西”。 这种情况较少使用,但有用例。 一个典型的例子是回调:如果你想将一个Rectangle传回一个回调,你可以使用Callback Callback ,因为Callback也能处理Rectangle

这是维基百科的相关文章 。

如果takeThing方法需要向list参数添加元素,则通配符版本将无法编译。

有趣的情况是,当您不添加到列表中时,两个版本似乎都可以编译和工作。

在这种情况下,如果要在列表中允许不同类型的动物(更灵活)和参数版本(当您需要列表中的固定动物类型:T类型)时,您将编写通配符版本。

例如, java.util.Collection声明:

 interface Collection { ... public boolean containsAll(Collection c); ... } 

假设您有以下代码:

 Collection c = Arrays.asList(1, 2); Collection i = Arrays.asList(1, 2, 3); i.containsAll(c); //compiles and return true as expected 

如果java.util.Collection将是:

 interface Collection { ... public boolean containsAll(Collection c); ... } 

上述测试代码无法编译, Collection API的灵活性也会降低。

值得注意的是, containsAll的后一个定义具有在编译时捕获更多错误的优点,例如:

 Collection c = Arrays.asList("1", "2"); Collection i = Arrays.asList(1, 2, 3); i.containsAll(c); //does not compile, the integer collection can't contain strings 

但是错过了有效的测试,使用Collectionc = Arrays.asList(1, 2);

Javagenerics通配符的使用受GET-PUT原则(也称为IN-OUT原则)的约束。 这表明: 当您只从结构中获取值时使用“扩展”通配符,当您仅将值放入结构时使用“超级”通配符,并且在执行这两者时不使用通配符。 这不适用于方法的返回类型。 不要使用通配符作为返回类型 。 见下面的例子:

 public static void copyContainerDataValues(Container source, Container destinationtion){ destination.put(source.get()); }