Java Generics - 这两个方法声明是等价的吗?

时间:2011-04-20 13:15:42

标签: java generics

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

public <T extends SomeBaseClass> void myMethod(Class<T> clz)

public void myMethod(Class<? extends SomeBaseClass> clz)

5 个答案:

答案 0 :(得分:12)

对于来电者:是的,他们是等同的。

对于方法内的代码:no。

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

答案 1 :(得分:3)

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

答案 2 :(得分:3)

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

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

例如

List<String> jedis = new ArrayList<String>();
jedis.add("Obiwan");

List<? extends CharSequence> ls = jedis
CharSequence obiwan = ls.get(0); //Ok
ls.add(new StringBuffer("Anakin")); //Not Ok

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

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

例如

List<Object> jedis = new ArrayList<Object>();
jedis.add("Obiwan"); 

List<? super String> 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<? extends Number> numbers){
    Object[] a = numbers.toArray();
    Arrays.sort(a);
    ListIterator<? extends Number> i = numbers.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((Number)a[j]); //Not Ok, you cannot be sure the list is of Number
    }
}

但如果你这样写,你就可以开展工作

public static <T extends Number> void sort(List<T> numbers){
    Object[] a = numbers.toArray();
    Arrays.sort(a);
    ListIterator<T> i = numbers.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((T)a[j]);
    }
}

你甚至可以使用带有通配符的集合来调用方法,这要归功于一个叫做捕获转换的东西:

List<? extends Number> ints = new ArrayList<Integer>();
List<? extends Number> floats = new ArrayList<Float>();
sort(ints);
sort(floats);

否则无法实现。

总之,正如其他人从来电者的观点来看他们是相似的,从实施的角度来看,他们不是。

答案 3 :(得分:1)

没有。最重要的是,我可以想到以下不同之处:

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

    class Foo {
        public <T extends SomeBaseClass> void myMethod(Class<T> clz) { }
    }
    
    class Bar extends Foo {
        public void myMethod(Class<? extends SomeBaseClass> clz) { }
    }
    

    无法编译:

      

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

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

    <T extends Comparable<T>> 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 extends Comparable<T>> 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;
    }
    

    编译。

答案 4 :(得分:0)

@Mark @Joachim @Michael

参见JLS3 5.1.10捕获转换中的示例

public static void reverse(List<?> list) { rev(list);}
private static <T> void rev(List<T> list){ ... }

因此<?>版本可以执行<T>版本可以执行的任何操作。

如果运行时具体化,这很容易接受。无论如何,List<?>对象必须是某个特定非通配符List<X>的{​​{1}}对象,我们可以在运行时访问此X。因此使用XList<?>

没有区别

使用类型擦除,我们无法访问List<T>T,因此也没有区别。我们可以在X中插入T - 但是如果List<T>对于调用是私有的,那么您可以在哪里获得T个对象,抹去?有两种可能性:

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

  2. 来自带外。程序员还有其他的安排,因此保证其他地方的对象可以是List<?>类型的调用。必须完成未经检查的转换才能覆盖编译器。同样,对T

  3. 做同样的事也没问题