虽然泛型方法的类型参数可以受边界限制,例如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 指出的那样,在某些情况下,泛型方法的类型参数被推断为通配符 capture,而不是具体类型或类型变量。请参阅他的答案,了解一个很好的getFooBar
实现示例,该示例使用代理来驱动他T
的未知点。
正如我们在评论中所讨论的,一个使用的例子getFooBar
造成了混乱,因为它不需要任何参数来推断T
。某些编译器在无上下文调用时会抛出错误getFooBar()
,而其他编译器则可以。我认为不一致的编译错误 - 以及调用是非法的事实FooBar.<?>getFooBar()
- 验证了我的观点,但事实证明这些都是红鲱鱼。
根据@MiserableVariable 的回答,我整理了一个新示例,该示例使用带有参数的泛型方法,以消除混淆。假设我们有接口Foo
和Bar
一个实现FooBarImpl
:
interface Foo { }
interface Bar { }
static class FooBarImpl implements Foo, Bar { }
我们还有一个简单的容器类,它包装了某种类型的实例来实现Foo
和Bar
。它声明了一个愚蠢的静态方法unwrap
,该方法接受 aFooBarContainer
并返回其所指对象:
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
——只知道它是 bounds 内的某种类型extends Foo & Bar
。
FooBarContainer.unwrap(unknownFooBarContainer); // T is a wildcard capture, ?
正如我所指出的,unwrap
使用通配符调用是非法的:
FooBarContainer.<?>unwrap(unknownFooBarContainer); // compiler error
我只能猜测这是因为通配符捕获永远不能相互匹配 -?
调用站点提供的参数是模棱两可的,没有办法说它应该专门匹配unknownFooBarContainer
.
因此,这是 OP 询问的语法的用例。调用返回类型的unwrap
引用。我们可以将该引用分配给or ,但不能同时分配给两者: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 { }
让我们实现一个不安全的方法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
}
在这里,当 newFoo2
被强制转换为时T
,它是“未经检查的”,这意味着由于类型擦除,运行时不知道它应该失败,即使在这种情况下它应该失败,因为T
was Foo1
。相反,堆是“污染的”,这意味着引用指向它们不应该被允许的对象。
失败发生在方法返回之后,当Foo2
实例尝试分配给foo1
具有 reifiable 类型的引用时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
}
现在它不会在呼叫站点爆炸。稍后当内容foo1List
被使用时它会爆炸。这就是堆污染变得更难调试的原因,因为异常堆栈跟踪不会将您指向实际问题。
当调用者本身在通用范围内时,它变得更加复杂。想象一下,List<Foo1>
我们没有得到 a ,而是得到 a List<T>
,将其放入 aMap<K, List<T>>
并将其返回给另一个方法。你明白我希望的想法。