关于Java泛型的下限用法:? super T
关于Java泛型的下限用法:? super T
我正在努力深入理解下界通配符的用法。我正在尝试编写一个通用的方法copy
,它将一个List
的内容复制到另一个List
中。我想出了这个方法的签名:
void copy(List dest, List extends T> src)
我认为这个签名详尽地解决了所有情况。然而,我看到在Java的Collections类中,方法的签名是这样的:
void copy(List super T> dest, List extends T> src)
我不明白为什么他们使用List super T> dest
而不是简单地List
。他们的签名有额外的灵活性吗?
Java中的泛型(Generics)是一种强大的特性,它允许我们在编译时进行类型检查,以确保代码的类型安全性。然而,有时我们可能会遇到一些令人困惑的问题,其中之一就是使用下界(Lower Bound)通配符时的类型推断问题。
在上述示例中,我们可以看到有两个copy方法的签名。第一个方法使用了下界通配符? super T
,而第二个方法则没有使用通配符。我们会发现,当我们尝试执行obj.copy(lm, lhm)
时,第一个方法会通过编译,而第二个方法则会报错。
那么为什么会出现这样的情况呢?原因在于类型推断的问题。在第二个方法中,由于没有使用下界通配符,编译器会根据参数的类型进行类型推断。在这种情况下,我们传入的参数类型是List<HashMap<String,String>>
和List<? extends HashMap<String,String>>
,由于参数类型不一致,编译器会报错。
而在第一个方法中,我们使用了下界通配符? super T
,这意味着我们可以接受T的超类作为参数。在这种情况下,编译器会将List<Map<String,String>>
作为List<? super HashMap<String,String>>
的超类型进行推断,因为HashMap<String,String>
是Map<String,String>
的子类。所以,编译器会通过编译。
为了解决这个问题,我们可以使用下界通配符? super T
,这样就可以接受T的超类作为参数。这样一来,我们就可以传入List<Map<String,String>>
作为List<? super HashMap<String,String>>
的参数,从而使代码通过编译。
我们可以看到,在Java中使用下界通配符? super T
可以解决类型推断的问题。通过使用下界通配符,我们可以接受T的超类作为参数,从而使代码具有更高的灵活性和可复用性。
在Java的泛型中,使用? super T
的原因是为了更明确地指定dest
应该接受的类型。与T
相比,? super T
只是一种风格上的差异,但这是更好的实践,这可以通过应用一些良好代码原则来看出:
- 显式意图:使用? super T
更清楚地显示dest
应该接受的类型。
- 模块化:你根本不需要查看src
上的类型约束就知道dest
可以接受哪些类型。
- PECS原则:生产者参数(下面的"in")应该使用extends
,而消费者参数(下面的"out")应该使用super
关键字。
使用? super T
也是Java教程推荐的(它们甚至使用了一个copy
函数):
引用如下:
“对于本讨论,将变量视为提供两个功能之一是有帮助的:
- “in”变量:它向代码提供数据。想象一个带有两个参数的copy
方法:copy(src, dest)
。 src
参数提供要复制的数据,因此它是“in”参数。
- “out”变量:它保存供其他地方使用的数据。在copy
示例中,copy(src, dest)
,dest
参数接受数据,因此它是“out”参数。
在决定是否使用通配符及何种类型的通配符时,可以使用“in”和“out”原则。以下是遵循的准则:
- 使用上边界通配符定义“in”变量,使用extends
关键字。
- 使用下边界通配符定义“out”变量,使用super
关键字。”
这个问题在这里也有答案。其中展示了它们等价的所有情况,这是特别有说服力的。