Bounded Type parameter (T extends) 和 Upper Bound Wildcard (? extends) 的区别

Difference between Bounded Type parameter (T extends) and Upper Bound Wildcard (? extends)

我知道已经发布了一个类似的问题,尽管我认为我的问题有些不同...

假设你有两种方法:

// Bounded type parameter
private static <T extends Number> void processList(List<T> someList) {

}

// Upper bound wildcard
private static void processList2(List<? extends Number> someList) {
    // ...
}

据我所知,这两种方法都接受参数,即 Number 类型的 List 子类型 List Number

但这两种方式到底有什么区别呢?

我能想到以下不同点:

a) 在方法中修改您的列表,考虑以下代码:

private static <T extends Number>void processList(List<T> someList)
{


   T t = someList.get(0);
   if ( t.getClass() == Integer.class )
   {
       Integer myNum = new Integer(4);
       someList.add((T) myNum);
   }

}

// Upper bound wildcard
private static void processList2(List<? extends Number> someList)
{
    Object o = someList.get(0);
    if ( o instanceof Integer )
    {
        Integer myNum = new Integer(4);
        someList.add(myNum); // Compile time error !!
    }
}

使用通配符无法向列表中添加元素!编译器告诉您它不知道 myNum 是什么。但是在第一种方法中,您可以通过首先检查 T 是否为 Integer 来添加 Integer ,没有编译时错误。

b) 第一种方法称为泛型方法。它遵循为 generic method 定义的语法。 方法定义中指定的上限用于限制参数类型。

第二个不一定称为泛型方法,它是一个普通方法,恰好接受泛型参数。 带有 extends 关键字的通配符 ? 用作 放宽 方法可以接受的类型的手段。

区别在于编译器方面。 在第一个上你可以使用类型(例如转换某些东西或将其用作绑定来调用另一个方法)而在第二个上你不能使用它。

编译时两种语法之间存在一些差异:

  • 使用第一种语法,您可以向 someList 添加元素,但使用第二种语法则不能。这通常称为 PECS 而不太常见的是 PUT 和 GET 原则。
  • 使用第一种语法,你有一个类型参数 T 的句柄,所以你可以用它来做一些事情,比如在类型 T 的方法中定义局部变量,转换引用到 T 类型,调用 T 表示的 class 中可用的方法,等等。但是使用第二种语法,您没有该类型的句柄,因此您可以做这些。
  • 第一个方法其实可以从第二个方法调用到 捕获通配符。这是 capture 最常见的方式 通过辅助方法的通配符。

    private static <T extends Number> void processList(List<T> someList) {
        T n = someList.get(0);
        someList.add(1,n); //addition allowed.   
    }
    
    private static void processList2(List<? extends Number> someList) {
        Number n = someList.get(0);
        //someList.add(1,n);//Compilation error. Addition not allowed.
        processList(someList);//Helper method for capturing the wildcard
    }
    

请注意,由于泛型是编译时糖,因此更广泛层面上的这些差异仅限于编译。

如果您想使用类型信息,请使用 bounded。使用通配符时,参数将显示为通用对象,您将无法调用基于该类型的方法。

public static <T extends Object> ListIterator<T> createListIterator(ListIterator<T> o)
{
    return new ListIteratorAdaptor<T>(o);
}

在JAVA中,通常与Generic一起使用的通配符有以下三种。每一个都在下面举例说明。

Upper-bounded 通配符:

? extends T :在上限通配符中,仅支持 T 或其子类型。 例如我们有一个 Animal class 并且有 Dog , Cat 作为它的子类型。所以遵循通用方法只会 接受 Data<Animal>, Data<Dog> and Data<Cat>

类型的参数
public static void add(Data<? extends Animal> animalData) {

}

Lower-bounded 通配符:

? super T :在 Lower-bounded 通配符中,仅支持 T 或其超类型。 我们用于定义 Lower-bounded 通配符的相同示例。假设我们有 Animal class 作为 super 或 parent class 和 Dog 作为它的 child class。现在下面的方法使用 Lower-bounded 通配符并且只接受类型

的参数
Data<Animal>, Data<Dog> and  Data<Object>

public static void add(Data<? super Dog> animalData) {
}

无限通配符:

? : 无限通配符支持所有类型。所以我们上面的示例方法可以采用

类型的参数
Data<Animal>, Data<Dog> , Data<Object> and Data<Cat>
public static void add(Data<?> animalData) {       
}