最近我遇到了一个与此定义类似的方法,但我并不完全了解其用法:
public static <T, U extends T> T foo(U u) { ... }
示例用法如下:
// Baz is just the containing class of foo()
Number n = Baz.foo(1);
将T
推论为Number
,将U
推论为Integer
的地方。但是当这优于例如此方法定义:
public static <T> T bar(T t) { ... }
如果我这样称呼它:
Number n = Baz.bar(2);
该代码仍然有效。 T
推断为Number
或Integer
(不知道参数类型(在此示例中为Integer
优于呼叫站点返回类型{{1})) })
答案 0 :(得分:9)
我认为,实际上,当方法的类型参数作为方法签名的一部分的参数化类型的类型参数出现时,只有 才有意义。
(至少,我无法快速提出一个例子,否则它会真的有意义)
您链接到的问题中也是如此,其中方法类型参数用作AutoBean
类中的类型参数。
一个小更新:
根据问题和其他答案中的讨论,该问题的核心可能是对类型参数的使用方式的误解。因此,该问题可以被视为Meaning of <T, U extends T> in java function declaration的重复,但希望有人会对此答案有所帮助。
最后,使用<T, U extends T>
模式的原因可以从参数化类型的继承关系中看出,详细说明可以是fairly complicated。例如,为了说明最相关的点:List<Integer>
不是是List<Number>
的子类型。
下面显示了一个可以在其中发挥作用的示例。它包含一个始终有效的“琐碎”实现(据我所知,这没有任何意义)。但是,当类型参数T
和U
也是方法参数和返回类型的类型参数时,类型绑定就变得有意义。在T extends U
中,您可以返回一个具有超类型作为类型参数的类型。否则,您将无法执行// Does not work
:
import java.util.ArrayList;
import java.util.List;
public class SupertypeMethod {
public static void main(String[] args) {
Integer integer = null;
Number number = null;
List<Number> numberList = null;
List<Integer> integerList = null;
// Always works:
integer = fooTrivial(integer);
number = fooTrivial(number);
number = fooTrivial(integer);
numberList = withList(numberList);
//numberList = withList(integerList); // Does not work
// Both work:
numberList = withListAndBound(numberList);
numberList = withListAndBound(integerList);
}
public static <T, U extends T> T fooTrivial(U u) {
return u;
}
public static <T, U extends T> List<T> withListAndBound(List<U> u) {
List<T> result = new ArrayList<T>();
result.add(u.get(0));
return result;
}
public static <T> List<T> withList(List<T> u) {
List<T> result = new ArrayList<T>();
result.add(u.get(0));
return result;
}
}
(当然,这看起来有些虚构,但我认为人们可以想象这确实有意义的场景)
答案 1 :(得分:3)
这在您要返回超级类型时很方便;就像您在示例中显示的一样。
您以U
作为输入并返回T
-这是U
的超类型;声明它的另一种方法是T super U
-但这在Java中是不合法的。
这应该是我实际意思的一个例子。假设一个非常简单的类,如:
static class Holder<T> {
private final T t;
public Holder(T t) {
this.t = t;
}
public <U super T> U whenNull(U whenNull){
return t == null ? whenNull : t;
}
}
定义的方法whenNull
无法编译,因为Java中不允许U super T
。
相反,您可以添加另一个类型参数并反转类型:
static class Holder<U, T extends U> {
private final T t;
public Holder(T t) {
this.t = t;
}
public U whenNull(U whenNull) {
return t == null ? whenNull : t;
}
}
用法为:
Holder<Number, Integer> n = new Holder<>(null);
Number num = n.whenNull(22D);
这允许返回超级类型;但是看起来很奇怪。我们在类声明中添加了另一种类型。
我们可以诉诸:
static class Holder<T> {
private final T t;
public Holder(T t) {
this.t = t;
}
public static <U, T extends U> U whenNull(U whenNull, Holder<T> holder) {
return holder.t == null ? whenNull : holder.t;
}
}
,甚至将此方法设为静态。
对于现有限制,您可以尝试执行以下操作:
Optional.ofNullable(<SomeSubTypeThatIsNull>)
.orElse(<SomeSuperType>)
答案 2 :(得分:2)
我的第一个念头是:哎呀,
Number n = Baz.bar(2);
随着Integer扩展Number,“将始终”起作用。因此,这样做没有任何优势。但是,如果您有一个不是抽象的超类怎么办?!
然后U extends T
允许您返回对象,该对象仅是超类型类的 ,而不能是子类的 !
类似
class B { }
class C extends B { }
现在,泛型方法也可以返回B的实例。如果只有T ...,则该方法只能返回C的实例。
换句话说:U extends T
允许您返回B 和 C的实例。T
单独:仅C!
但是,当然,当您查看某些特定的B和C时,上面的方法是有意义的。但是,当方法(实际上)只是返回B的实例时,为什么在这里首先需要泛型?
因此,我同意以下问题:我也看不到此构造的实用值。除非有人反思,但即使如此,我也不会因为U extends T
而只能使用 的声音设计。
答案 3 :(得分:2)
第一种方法
public static <T, U extends T> T foo(U u) { ... }
表示T
和U
可以是不同的类型。即一种是T
,另一种是U
子类型的T
。
第二个例子
public static <T> T bar(T t) { ... }
bar(T t)
必须返回与参数t
相同的类型。它不能返回参数类型超类的对象。只有您的第一个变体才有可能。