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