泛型类型和通配符类型的区别

IT小君   2021-09-26T06:23:53

我是 Generic 的新手,我的问题是:两个函数之间有什么区别:

功能一:

public static <E> void funct1  (List<E> list1) {

}

功能二:

public static void funct2(List<?> list) {

}
点击广告,支持我们为你提供更好的服务
评论(8)
IT小君

第一个签名说:list1 是一个 Es 的列表。

第二个签名说:list 是某种类型的实例列表,但我们不知道类型。

当我们尝试更改方法时,差异变得很明显,因此它需要第二个参数,应该将其添加到方法内的列表中:

import java.util.List;

public class Experiment {
    public static <E> void funct1(final List<E> list1, final E something) {
        list1.add(something);
    }

    public static void funct2(final List<?> list, final Object something) {
        list.add(something); // does not compile
    }
}

第一个效果很好。并且您不能将第二个参数更改为实际可以编译的任何内容。

实际上,我刚刚找到了一个更好的差异演示:

public class Experiment {
    public static <E> void funct1(final List<E> list) {
        list.add(list.get(0));
    }

    public static void funct2(final List<?> list) {
        list.add(list.get(0)); // !!!!!!!!!!!!!! won't compile !!!!!!!!!
    }
}

人们可能会问为什么我们需要<?>它,因为它只限制了我们可以用它做什么(就像@Babu_Reddy_H 在评论中所做的那样)。我看到通配符版本的以下好处:

  • 调用者不必知道他传入的对象。例如,如果我有一个列表映射:Map<String, List<?>>我可以将它的值传递给您的函数,而无需指定列表元素的类型。所以

  • 如果我分发像这样参数化的对象,我会主动限制人们对这些对象的了解以及他们可以用它做什么(只要他们远离不安全的投射)。

当我将它们合并这两个是有意义的:List<? extends T>例如,考虑一个方法List<T> merge(List<? extends T>, List<? extends T>),它将两个输入列表合并为一个新的结果列表。当然你可以再引入两个类型参数,但你为什么要这么做?这将结束指定的事情。

  • 最后通配符可以有下限,因此使用列表可以使add方法起作用,get而不会给您任何有用的东西。当然这会引发下一个问题:为什么泛型没有下界?

有关更深入的答案,请参阅:何时使用泛型方法以及何时使用通配符?http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ203

2021-09-26T06:23:53   回复
IT小君

泛型使集合类型更安全。

List<E>:E 这里是类型参数,可以用来判断列表的内容类型,但是有No办法在runtime.

Generics are checked only during compilation time.

<? extends String>:这是专门内置到java中的,用于处理类型参数的问题。"? extends String"意味着这个 List 可以有

objects which IS-A String.

例如:

动物类狗类扩展动物老虎类扩展动物

因此,使用 "public void go(ArrayList<Animal> a)"will NOT acceptDog 或 Tiger 作为其内容,但使用 Animal。

"public void go(ArrayList<? extends Animal> a)" 是制作 ArrayList take in Dog and Tiger type.

检查 Head First Java 中的引用。

2021-09-26T06:23:53   回复
IT小君

第一个是接受参数的函数,该参数必须是 E 类型的项目列表。

未定义第二个示例类型

List<?> list

所以你可以传递任何类型的对象的列表。

2021-09-26T06:23:54   回复
IT小君

作为参数类型的列表表示参数必须是具有任何对象类型的项目列表。此外,您可以绑定E参数以声明对函数体内列表项的引用。

作为参数类型的 List 具有相同的语义,除了使用Object. 其他帖子给出了额外的细微差别。

2021-09-26T06:23:54   回复
IT小君

我通常会解释 < E > 和 < ? > 通过与逻辑量化的比较,即全称量化和存在量化。

  • 对应于“forall E, ...”
  • 对应于“存在某物(由 表示)使得......”

因此,以下泛型方法声明意味着,对于所有类类型E,我们定义funct1

public static <E> void funct1  (List<E>; list1) {

}

以下泛型方法声明意味着,对于由 < 表示的某些现有类>,我们定义funct2.

public static void funct2(List<?> list) {

}
2021-09-26T06:23:54   回复
IT小君

(由于您的编辑)这两个函数签名对外部代码具有相同的效果——它们都将 anyList作为参数。通配符相当于只使用一次的类型参数。

2021-09-26T06:23:54   回复
IT小君

除了前面提到的那些差异之外,还有一个额外的差异:您可以为泛型方法的调用显式设置类型参数:

List<Apple> apples = ...
ClassName.<Banana>funct2(apples); // for some reason the compiler seems to be ok
                               // with type parameters, even though the method has none

ClassName.<Banana>funct1(apples); // compiler error: incompatible types: List<Apple>
                                  //                 cannot be converted to List<Banana>

ClassName是包含方法的类的名称。)

2021-09-26T06:23:55   回复
IT小君

在这种情况下,通配符 (?) 和类型参数 (E) 都会为您做同样的事情。基于用例有一定的优势。假设您想要一种可能有多个参数的方法,例如:

public void function1(ArrayList<?> a, ArrayList<?> b){
 // some process
}

public <T> void function2(ArrayList<T> a, ArrayList<T> b){
 // some process
}

在 function1 中,a 可以是 String 的 AL,b 可以是 Integer 的 AL,因此无法控制两个参数的类型,但这对 function2 来说很容易。 如果我们想稍后在方法或类中使用类型,我们应该使用类型参数(函数 2)

WildCard 和 Type 参数有一些特性:

通配符(?)

  1. 它支持类型的上限和下限,而类型参数 (E) 仅支持上限。

类型参数(E)

  1. 有时我们不需要传递实际的类型:

    ArrayList<Integer> ai = new ArrayList<Integer>();
    ArrayList<Double>  ad = new ArrayList<Double>();
    function2(ai, ad);
    //It will compile and the T will be Number.
    

在这种情况下,编译器会根据实际参数的类型为我们推断类型参数

2021-09-26T06:23:55   回复