Java 为什么使用通配符捕获辅助方法?

Java 为什么使用通配符捕获辅助方法?,java,generics,wildcard,Java,Generics,Wildcard,指: 它说创建一个助手方法来捕获通配符 public void foo(List<?> i) { fooHelper(i); } private <T> void fooHelper(List<T> l) { l.set(0, l.get(0)); } public void foo(列表一){ 食物助理(i);; } 私人助理(列表l){ l、 set(0,l.get(0)); } 仅使用下面的函数不会产生

指:

它说创建一个助手方法来捕获通配符

public void foo(List<?> i) {
    fooHelper(i);
}        
private <T> void fooHelper(List<T> l) {
    l.set(0, l.get(0));
}
public void foo(列表一){
食物助理(i);;
}        
私人助理(列表l){
l、 set(0,l.get(0));
}
仅使用下面的函数不会产生任何编译错误,而且似乎也是这样。我不明白的是:为什么你不直接使用这个,避免使用助手

public <T> void foo(List<T> l) {
    l.set(0, l.get(0));
}
public void foo(列表l){
l、 set(0,l.get(0));
}
我认为这个问题可以归结为:通配符和泛型之间有什么区别?所以,我去了这个:。 它说要使用类型参数:

1) 如果要对不同类型的方法参数强制执行某种关系,则不能使用通配符,必须使用类型参数

但是,这不正是通配符with helper函数的实际用途吗?它不是通过设置和获取未知值在不同类型的方法参数上强制执行关系吗

我的问题是:如果您必须在不同类型的方法arg上定义需要关系的东西,那么为什么要首先使用通配符,然后使用辅助函数呢


合并通配符似乎是一种很有技巧的方法。

在这种特殊情况下,这是因为该方法要求类型与列表中的类型相同

如果没有helper方法,编译器不知道
是否与
列表
get(int)
的返回相同,因此会出现编译器错误:

The method set(int, capture#1-of ?) in the type List<capture#1-of ?> is not applicable for the arguments (int, capture#2-of ?)
类型列表中的方法集(int,capture#1-of?)不适用于参数(int,capture#2-of?)
对于helper方法,您告诉编译器,类型是相同的,我只是不知道类型是什么

那么为什么要使用非助手方法呢?


泛型直到Java5才被引入,所以有很多代码早于泛型。Java 5之前的
List
现在是
List
,因此如果您试图在通用编译器中编译旧代码,如果无法更改方法签名,则必须添加这些助手方法。

我同意:删除助手方法并键入公共API。没有理由不这样做,也没有理由不这样做


仅总结一下对通配符版本的帮助器的需求:虽然这对我们人类来说是显而易见的,但编译器不知道从
l.get(0)
返回的未知类型与列表本身的未知类型相同。ie它没有考虑到
set()
调用的参数来自与目标相同的列表对象,因此它必须是一个安全的操作。它只注意到从
get()
返回的类型是未知的,目标列表的类型是未知的,并且两个未知的不能保证是相同的类型。

您是正确的,我们不必使用通配符版本

这取决于哪个API看起来/感觉“更好”,这是主观的

    void foo(List<?> i) 
<T> void foo(List<T> i)
void foo(列表一)
无效foo(列表一)
我会说第一版更好

如果有界限

    void foo(List<? extends Number> i) 
<T extends Number> void foo(List<T> i)
void foo(List用了一些段落来解释为什么人们更喜欢提供通配符方法
public
ly,而使用泛型方法
private
ly。具体来说,他们说(关于
public
泛型方法):

这是不可取的,因为它向调用者公开了实现信息

他们这么说是什么意思?到底是什么东西暴露在其中而不是另一个

您知道可以将类型参数直接传递给方法吗?如果您在
Foo
类上有一个静态方法
Foo create()
——是的,这对静态工厂方法非常有用——那么您可以将其作为
Foo.create()调用
。您通常不需要或不想这样做,因为Java有时可以从提供的任何参数推断这些类型。但事实是,您可以显式提供这些类型

因此,泛型的
void foo(List i)
实际上在语言级别采用了两个参数:列表的元素类型和列表本身。我们修改了方法契约,只是为了在实现方面节省一些时间

很容易认为
只是更明确的泛型语法的简写,但我认为Java的符号实际上掩盖了这里真正发生的事情。让我们把它翻译成类型理论语言:

/*Java*//*类型理论*/
列表~∃T.列表
void foo(列表l)~~(∃T.列表)->()
void foo(列表l)~~∀T.(列表->()
列表
这样的类型被称为存在类型。
意味着存在某种类型,但我们不知道它是什么。在类型理论方面,
∃T.
的意思是“存在一些T”,这基本上就是我在前一句中所说的——我们刚刚给了那个类型一个名称,尽管我们仍然不知道它是什么

在类型理论中,函数有类型
A->B
,其中
A
是输入类型,
B
是返回类型。(出于愚蠢的原因,我们将
void
写为
()
)注意,在第二行,我们的输入类型与我们讨论的存在列表相同

第三行发生了一些奇怪的事情!在Java方面,看起来我们只是简单地命名了通配符(这是一个不错的直觉)。在类型理论方面,我们说了一些与前一行非常相似的事情:对于调用方选择的任何类型,我们都会接受该类型的列表。(
∀T.
实际上被理解为“所有T”。)但是
T的范围现在完全不同了——括号已经移动到包括