我是 Generic 的新手,我的问题是:两个函数之间有什么区别:
功能一:
public static <E> void funct1 (List<E> list1) {
}
功能二:
public static void funct2(List<?> list) {
}
我是 Generic 的新手,我的问题是:两个函数之间有什么区别:
功能一:
public static <E> void funct1 (List<E> list1) {
}
功能二:
public static void funct2(List<?> list) {
}
泛型使集合类型更安全。
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 accept
Dog 或 Tiger 作为其内容,但使用 Animal。
"public void go(ArrayList<? extends Animal> a)"
是制作 ArrayList take in Dog and Tiger type.
检查 Head First Java 中的引用。
第一个是接受参数的函数,该参数必须是 E 类型的项目列表。
未定义第二个示例类型
List<?> list
所以你可以传递任何类型的对象的列表。
作为参数类型的列表表示参数必须是具有任何对象类型的项目列表。此外,您可以绑定E
参数以声明对函数体内列表项的引用。
作为参数类型的 List 具有相同的语义,除了使用Object
. 其他帖子给出了额外的细微差别。
我通常会解释 < E > 和 < ? > 通过与逻辑量化的比较,即全称量化和存在量化。
因此,以下泛型方法声明意味着,对于所有类类型E,我们定义funct1
public static <E> void funct1 (List<E>; list1) {
}
以下泛型方法声明意味着,对于由 < 表示的某些现有类?>,我们定义funct2
.
public static void funct2(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
}
在 function1 中,a 可以是 String 的 AL,b 可以是 Integer 的 AL,因此无法控制两个参数的类型,但这对 function2 来说很容易。 如果我们想稍后在方法或类中使用类型,我们应该使用类型参数(函数 2)
WildCard 和 Type 参数有一些特性:
通配符(?)
类型参数(E)
有时我们不需要传递实际的类型:
ArrayList<Integer> ai = new ArrayList<Integer>();
ArrayList<Double> ad = new ArrayList<Double>();
function2(ai, ad);
//It will compile and the T will be Number.
在这种情况下,编译器会根据实际参数的类型为我们推断类型参数
第一个签名说: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