Java Generics – 这两个方法声明是等效的吗?

给定一些SomeBaseClass类,这两个方法声明是否等效?

 public  void myMethod(Class clz) 

 public void myMethod(Class clz) 

对于呼叫者:是的,它们是等效的。

对于方法内部的代码:没有。

区别在于,在第一个示例的代码中,您可以使用类型T(例如,用于保存由clz.newInstance()创建的对象),而在第二个示例中则不能。

不,他们不是。 使用第一个定义,您可以在方法定义中使用类型T,例如创建一个ArrayList或返回T.使用第二个定义,这是不可能的。

有界通配符受到一定限制,以避免堆污染。

当你使用通配符? 扩展X你知道你可以阅读通用信息,但你不能写。

例如

 List jedis = new ArrayList(); jedis.add("Obiwan"); List ls = jedis CharSequence obiwan = ls.get(0); //Ok ls.add(new StringBuffer("Anakin")); //Not Ok 

当您尝试将CharSequence(即StringBuffer)添加到集合时,编译器避免了堆污染。 因为编译器无法确定(由于通配符)集合的实际实现是StringBuffer类型。

你用的时候? 超级X你知道你可以写一般信息,但你不能确定你所读的类型。

例如

 List jedis = new ArrayList(); jedis.add("Obiwan"); List ls = jedis; ls.add("Anakin"); //Ok String obiwan = ls.get(0); //Not Ok, we can´t be sure list is of Strings. 

在这种情况下,由于通配符,编译器知道集合的实际实现可以是String的祖先中的任何内容。 因此,它不能保证你将得到的将是一个字符串。 对?

在具有有界通配符的任何声明中,您也会遇到同样的限制。 这些通常被称为get / put原则。

通过使用类型参数T,您可以更改故事,从方法的角度来看,您没有使用有界通配符而是实际类型,因此您可以“获取”并“将”放入类的实例中,编译器不会抱怨。

例如,考虑Collections.sort方法中的代码。 如果我们编写如下的方法,我们会得到一个编译错误:

 public static void sort(List numbers){ Object[] a = numbers.toArray(); Arrays.sort(a); ListIterator i = numbers.listIterator(); for (int j=0; j 

但如果你这样写,你可以做的工作

 public static  void sort(List numbers){ Object[] a = numbers.toArray(); Arrays.sort(a); ListIterator i = numbers.listIterator(); for (int j=0; j 

你甚至可以通过一个叫做捕获转换的东西来调用带有通配符的集合的方法:

 List ints = new ArrayList(); List floats = new ArrayList(); sort(ints); sort(floats); 

否则无法实现这一点。

总而言之,正如其他人从呼叫者的角度所说的那样,从实施的角度来看,他们并非同样如此。

不。最重要的是,我可以想到以下差异:

  1. 这两个版本不是覆盖等价的。 例如,

     class Foo { public  void myMethod(Class clz) { } } class Bar extends Foo { public void myMethod(Class clz) { } } 

    不编译:

    名称冲突:类型为Bar的myMethod(Class)方法与Foo类型的myMethod(Class)具有相同的擦除但不覆盖它

  2. 如果类型参数在方法签名中出现多次,则它始终表示相同的类型,但如果通配符出现多次,则每次出现可能引用不同的类型。 例如,

     > T max(T a, T b) { return a.compareTo(b) > 0 ? a : b; } 

    编译,但是

     Comparable max(Comparable a, Comparable b) { return a.compareTo(b) > 0 ? a : b; } 

    没有,因为后者可能被称为

     max(Integer.MAX_VALUE, "hello"); 
  3. 方法体可以使用类型参数来引用调用者使用的实际类型,但不使用通配符类型。 例如:

     > T max(T... ts) { if (ts.length == 0) { return null; } T max = ts[0]; for (int i = 1; i < ts.length; i++) { if (max.compareTo(ts[i]) > 0) { max = ts[i]; } } return max; } 

    编译。

@Mark @Joachim @Michael

请参阅JLS3 5.1.10捕获转换中的示例

 public static void reverse(List list) { rev(list);} private static  void rev(List list){ ... } 

因此版本可以执行版本可以执行的任何操作。

如果运行时具体化,这很容易接受。 无论如何, List对象必须是某个特定非通配符XList对象,我们可以在运行时访问此X 所以使用ListList没有区别

对于类型擦除,我们无法访问TX ,因此也没有区别。 我们可以将一个T插入到List – 但是如果T对于调用是私有的,那么在哪里可以获得T对象擦除? 有两种可能性:

  1. T对象已存储在List 。 所以我们自己操纵元素。 正如reverse/rev示例所示,对List执行此操作也没有问题

  2. 它出现在带外 。 程序员还有其他的安排,所以其他地方的对象保证为调用类型为T 必须完成未经检查的转换才能覆盖编译器。 同样,对List做同样的事情也没问题