53

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

功能一:

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

}

功能二:

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

}
4

8 回答 8

37

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

第二个签名说: 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>),它将两个输入列表合并到一个新的结果列表中。当然,您可以再引入两个类型参数,但您为什么要这样做呢?这将是过度指定的事情。

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

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

于 2012-06-08T05:07:21.500 回答
7

泛型使集合更安全。

List<E>: E 这里是Type Parameter,可以用来确定列表的内容类型,但是有No办法在runtime.

Generics are checked only during compilation time.

<? extends String>:这是专门构建到 java 中的,用于处理类型参数的问题。"? extends String"表示这个 List 可以有

objects which IS-A String.

例如:

动物类 Dog 类扩展 Animal Tiger 类扩展 Animal

所以使用 "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 中的引用。

于 2012-06-08T05:03:54.560 回答
1

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

List 作为参数类型具有相同的语义,除了没有其他方法可以声明对列表中项目的引用,而只能使用Object. 其他帖子给出了额外的细微差别。

于 2012-06-08T04:42:23.493 回答
1

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

  • 对应于“forall E, ...”
  • 对应于“存在某种东西(用 表示)使得......”

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

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

}

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

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

}
于 2014-12-28T12:13:33.673 回答
0

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

第二个示例类型未定义

List<?> list

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

于 2012-06-08T05:21:32.427 回答
0

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

于 2012-06-08T07:28:38.160 回答
0

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

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是包含方法的类的名称。)

于 2015-05-24T10:32:08.393 回答
0

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

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

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

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

WildCard 和 Type 参数中有一些功能:

通配符(?)

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

类型参数(E)

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

    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-23T13:55:55.440 回答