所以我正在阅读仿制药,以便重新熟悉这些概念,特别是在涉及通配符的地方,因为我几乎没有使用它们或碰到它们。从我所做的阅读中我无法理解为什么他们使用通配符。我不断遇到的一个例子是:
void printCollection( Collection<?> c ) {
for (Object o : c){
System.out.println(o);
}
}
为什么不把它写成:
<T> void printCollection( Collection<T> c ) {
for(T o : c) {
System.out.println(o);
}
}
来自oracle网站的另一个例子:
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
为什么不写为
public static <T extends Number> double sumOfList(List<T> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
我错过了什么吗?
答案 0 :(得分:3)
为什么让事情比他们需要的更复杂?这些示例演示了the simplest thing that could possibly work - 这些示例并未尝试说明通用方法。
为什么不把它写成:
<T> void printCollection( Collection<T> c ) { for(T o : c) { System.out.println(o); } }
因为System.out.println()
可以接受对象,所以不需要更具体的内容。
为什么不写为
public static <T extends Number> double sumOfList(List<T> list) { double s = 0.0; for (Number n : list) s += n.doubleValue(); return s; }
同样,因为您不需要为每个不同的T extends Number
使用不同的参数化。接受List<? extends Number>
的非通用方法就足够了。
答案 1 :(得分:3)
如果方法参数类型具有带上限的第一级通配符,则可以使用类型参数替换它。
反例(通配符不能被类型参数替换)
List<?> foo() // wildcard in return type
void foo(List<List<?>> arg) // deeper level of wildcard
void foo(List<? super Number> arg) // wildcard with lower bound
现在可以通过通配符或类型参数
解决的情况 void foo1(List<?> arg)
<T> void foo2(List<T> arg)
通常认为foo1()
比foo2()
更时尚。这可能有点主观。我个人认为foo1()
签名更容易理解。 foo1()
在行业中占绝大多数,因此遵循惯例会更好。
foo1()
还会更加抽象地对待arg
,因为您无法在arg.add(something)
中轻松foo1()
。当然,这可以很容易地解决(即将arg传递给foo2()
!)。通常的做法是,公共方法看起来像foo1()
,内部转向私有foo2()
。
在某些情况下,通配符不会出现并且需要类型参数:
<T> void foo(List<T> foo1, List<T> foo2); // can't use 2 wildcards.
到目前为止,这个讨论是关于方法签名的。在其他地方,通配符在不能引入类型参数的情况下是必不可少的。
答案 2 :(得分:3)
来自Oracle:
出现的一个问题是:何时应该使用泛型方法,何时应该使用通配符类型?为了理解答案,我们来看一下Collection库中的一些方法。
interface Collection<E> {
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}
我们可以在这里使用泛型方法:
interface Collection<E> {
public <T> boolean containsAll(Collection<T> c);
public <T extends E> boolean addAll(Collection<T> c);
// Hey, type variables can have bounds too!
}
但是,在containsAll和addAll中,类型参数T仅使用一次。返回类型不依赖于类型参数,也不依赖于方法的任何其他参数(在这种情况下,只有一个参数)。这告诉我们类型参数用于多态;它唯一的作用是允许在不同的调用站点使用各种实际的参数类型。如果是这种情况,则应使用通配符。通配符旨在支持灵活的子类型,这是我们在这里要表达的内容。
所以对于第一个例子来说,这是因为操作不依赖于类型。
对于第二个,这是因为它只取决于Number类。