我是 Generic 的新手,我的问题是:两个函数有什么区别:
功能一:
public static <E> void funct1 (List<E> list1) {
}
功能二:
public static void funct2(List<?> list) {
}
第一个签名说: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>)
,它将两个输入列表合并到一个新的结果列表中。当然,您可以再引入两个类型参数,但您为什么要这样做呢?这将是过度指定的事情。
add
方法起作用,get
而不会给您任何有用的东西。当然,这引发了下一个问题:为什么泛型没有下界?有关更深入的答案,请参阅:何时使用泛型方法以及何时使用通配符?和http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ203
泛型使集合更安全。
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 accept
Dog 或 Tiger 作为其内容,但 Animal。
"public void go(ArrayList<? extends Animal> a)"
是什么需要做ArrayList take in Dog and Tiger type.
检查 Head First Java 中的引用。
列表作为参数类型表示参数必须是具有任何对象类型的项目列表。此外,您可以绑定E
参数以声明对函数体内列表项的引用。
List 作为参数类型具有相同的语义,除了没有其他方法可以声明对列表中项目的引用,而只能使用Object
. 其他帖子给出了额外的细微差别。
我通常会解释 < E > 和 < ? > 通过与逻辑量化的比较,即全称量化和存在量化。
因此,下面的泛型方法声明意味着,对于所有类类型E,我们定义funct1
public static <E> void funct1 (List<E>; list1) {
}
以下泛型方法声明意味着,对于某些由 < ?表示的现有类 >,我们定义funct2
.
public static void funct2(List<?> list) {
}
第一个是接受参数的函数,该参数必须是 E 类型的项目列表。
第二个示例类型未定义
List<?> list
所以你可以传递任何类型的对象的列表。
(自您的编辑以来)这两个函数签名对外部代码具有相同的效果——它们都将 anyList
作为参数。通配符相当于只使用一次的类型参数。
除了前面提到的那些区别之外,还有一个额外的区别:您可以显式设置泛型方法调用的类型参数:
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
是包含方法的类的名称。)
在这种情况下,通配符 (?) 和类型参数 (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 参数中有一些功能:
通配符(?)
类型参数(E)
有时我们不需要传递实际的类型 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.
在这种情况下,编译器根据实际参数的类型为我们推断类型参数