Java泛型——这两个方法声明是等价的吗?



给定某个类SomeBaseClass,这两个方法声明是等价的吗?

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

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

对于调用者:是的,它们是等价的。

方法内部的代码:no.

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

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

有界通配符受某些限制以避免堆污染。

当你使用通配符?扩展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类型的

当你使用?你知道你可以写一般的信息,但是你不能确定你读到的是什么类型。

例如

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的祖先中的任何对象。因此,它不能保证您得到的将是一个String。对吧?

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

通过使用类型参数T,您改变了故事,从方法的角度来看,您不是使用有界通配符,而是使用实际类型,因此您可以"获取"one_answers"放入"内容到类的实例中,编译器不会抱怨。

例如,考虑集合中的代码。排序方法。如果按照如下方式编写方法,将得到编译错误:

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);

这是不可能实现的。

总之,正如其他人所说,从调用者的角度来看,它们是相似的,但从实现的角度来看,它们不是。

No。在我的脑海中,我能想到以下不同之处:

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

    编译。

@Mark @Joachim @Michael

参见JLS3 5.1.10 Capture Conversion中的示例

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

因此<?>版本可以做<T>版本可以做的任何事情。

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

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

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

  2. 它来自带外。程序员还做了其他安排,以便保证其他地方的对象在调用时为T类型。必须进行未检查的强制转换以覆盖编译器。同样,对List<?>

  3. 做同样的事情没有问题

最新更新