虽然通用方法的类型参数可以受限制,例如
extends Foo &Bar,但它们最终由调用者决定。致电时
getFooBar(),呼叫站点已经知道
T要解决的问题。通常,这些类型参数将由编译器 推断,这就是为什么您通常不需要指定它们的原因,如下所示:
FooBar.<FooAndBar>getFooBar();
但是即使
T被推断为
FooAndBar,这实际上是幕后发生的事情。
因此,要回答您的问题,请使用以下语法:
Foo&Bar bothFooAndBar = FooBar.getFooBar();
在实践中永远不会有用。原因是呼叫者 必须已经知道了 什么
T。要么
T是一些具体类型:
FooAndBar bothFooAndBar = FooBar.<FooAndBar>getFooBar(); // T is FooAndBar
或者,
T是一个未解析的类型参数,我们在其范围内:
<U extends Foo & Bar> void someGenericMethod() { U bothFooAndBar = FooBar.<U>getFooBar(); // T is U}另一个例子:
class SomeGenericClass<V extends Foo & Bar> { void someMethod() { V bothFooAndBar = FooBar.<V>getFooBar(); // T is V }}从技术上讲,这就是答案。但我也想指出,您的示例方法
getFooBar本质上是不安全的。记住,调用者决定要做什么
T,而不是方法。由于
getFooBar不采用与参数相关的任何参数
T,并且由于类型擦除,因此唯一的选择是
null通过进行未经检查的强制转换来返回或“说谎”,从而冒堆污染的风险。一个典型的解决方法是
getFooBar采用一个
Class<T>参数,或者
FooFactory<T>例如。
更新资料
当我断言调用方
getFooBar必须始终知道是什么时,事实证明我错了
T。正如@MiserableVariable指出的那样,在某些情况下,泛型方法的类型参数被推断为
通配符捕获 ,而不是具体的类型或类型变量。
正如我们在评论中所讨论的,一个使用
getFooBar混乱的示例,因为它不需要任何参数来推断
T。某些编译器在无上下文调用时抛出错误,
getFooBar()而其他编译器则对此表示满意。我
认为 不一致的编译错误以及调用
FooBar.<?>getFooBar()是非法的事实证实了我的观点,但事实证明这些都是红色鲱鱼。
基于@MiserableVariable的答案,我整理了一个新示例,该示例使用带有参数的泛型方法来消除混乱。假设我们有接口
Foo和
Bar和实施
FooBarImpl:
interface Foo { }interface Bar { }static class FooBarImpl implements Foo, Bar { }我们还有一个简单的容器类,用于包装实现
Foo和的某种类型的实例
Bar。它声明一个愚蠢的静态方法
unwrap,采用a
FooBarContainer并返回其引用:
static class FooBarContainer<T extends Foo & Bar> { private final T fooBar; public FooBarContainer(T fooBar) { this.fooBar = fooBar; } public T get() { return fooBar; } static <T extends Foo & Bar> T unwrap(FooBarContainer<T> fooBarContainer) { return fooBarContainer.get(); }}现在假设我们有一个通配符参数化类型
FooBarContainer:
FooBarContainer<?> unknownFooBarContainer = ...;
我们被允许
unknownFooBarContainer进入
unwrap。这表明我先前的断言是错误的,因为呼叫站点不知道是什么
T-只是它是边界内的某种类型
extendsFoo & Bar。
FooBarContainer.unwrap(unknownFooBarContainer); // T is a wildcard capture, ?
如前所述,
unwrap使用通配符进行调用是非法的:
FooBarContainer.<?>unwrap(unknownFooBarContainer); // compiler error
我只能猜测这是因为通配符捕获永远无法匹配-
?调用站点提供的参数是模棱两可的,没有办法说它应该与类型的通配符特别匹配
unknownFooBarContainer。
因此,这是OP询问的语法的用例。调用
unwrap的
unknownFooBarContainer返回类型的引用
? extends Foo &Bar。我们可以将该引用分配给
Foo或
Bar,但不能两者都使用:
Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);Bar bar = FooBarContainer.unwrap(unknownFooBarContainer);
如果由于某种原因
unwrap昂贵,而我们只想调用一次,我们将被迫强制转换:
Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);Bar bar = (Bar)foo;
因此,这是假设语法派上用场的地方:
Foo&Bar fooBar = FooBarContainer.unwrap(unknownFooBarContainer);
这只是一个相当晦涩的用例。允许使用这种语法,无论好坏,都会有非常广泛的含义。这将在不需要的地方打开滥用的空间,而且为什么语言设计人员没有实现这种目的也是完全可以理解的。但是我仍然认为思考很有趣。
关于堆污染的注意事项
主要针对@MiserableVariable以下是不安全方法如何
getFooBar导致堆污染及其含义的演练。给定以下接口和实现:
interface Foo { }static class Foo1 implements Foo { public void foo1Method() { }}static class Foo2 implements Foo { }让我们实现一个unsafe方法
getFoo,
getFooBar该示例类似于但已简化:
@SuppressWarnings("unchecked")static <T extends Foo> T getFoo() { //unchecked cast - ClassCastException is not thrown here if T is wrong return (T)new Foo2();}public static void main(String[] args) { Foo1 foo1 = getFoo(); //ClassCastException is thrown here}在这里,当新的
Foo2强制转换
T,它是“未登记”,因为类型擦除意味着运行时不知道它应该会失败,即使它应该在这种情况下,由于
T是
Foo1。相反,堆是“污染的”,这意味着引用指向不应该允许的对象。
当
Foo2实例尝试将实例分配给
foo1具有可更改类型的引用时,方法返回后会发生失败
Foo1。
您可能会想,“好吧,它在呼叫站点爆炸了,而不是方法,大不了了。” 但是,当涉及更多泛型时,它很容易变得更加复杂。例如:
static <T extends Foo> List<T> getFooList(int size) { List<T> fooList = new ArrayList<T>(size); for (int i = 0; i < size; i++) { T foo = getFoo(); fooList.add(foo); } return fooList;}public static void main(String[] args) { List<Foo1> foo1List = getFooList(5); // a bunch of things happen //sometime later maybe, depending on state foo1List.get(0).foo1Method(); //ClassCastException is thrown here}现在,它不会在呼叫站点爆炸。当
foo1Listget
的内容被使用时,它会炸毁。这就是堆污染变得更难以调试的方式,因为异常stacktrace不会将您引向实际的问题。
当调用者本身在通用范围内时,它变得更加复杂。想象一下,而不是
List<Foo1>得到a
List<T>,而是将其放入a
Map<K,List<T>>并将其返回另一种方法。你明白我的想法。



