而泛型方法的类型参数可以通过边界来限制,例如extends Foo & Bar
,它们最终由调用者决定。你打电话时getFooBar()
,调用站点已经知道什么T
正在解决。通常,这些类型参数将是inferred由编译器指定,这就是为什么您通常不需要指定它们,如下所示:
FooBar.<FooAndBar>getFooBar();
但即使当T
被推断为FooAndBar
,这确实是幕后发生的事情。
因此,为了回答你的问题,这样的语法如下:
Foo&Bar bothFooAndBar = FooBar.getFooBar();
在实践中永远不会有用。原因是调用者一定已经知道 what T
是。任何一个T
是一些具体类型:
FooAndBar bothFooAndBar = FooBar.<FooAndBar>getFooBar(); // T is FooAndBar
Or, 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
,并且因为类型擦除 http://docs.oracle.com/javase/tutorial/java/generics/erasure.html,它唯一的选择就是返回null
或者通过未经检查的演员来“撒谎”,冒着风险堆污染 https://softwareengineering.stackexchange.com/a/156008/33756。典型的解决方法是getFooBar
采取Class<T>
论证,或者是一个FooFactory<T>
例如。
Update
事实证明,当我断言调用者时我错了getFooBar
必须始终知道什么T
是。正如 @MiserableVariable 指出的,在某些情况下,泛型方法的类型参数被推断为通配符捕获,而不是具体类型或类型变量。See 他的回答 https://stackoverflow.com/a/14632995/697449一个很好的例子getFooBar
使用代理来实现他的观点T
未知。
正如我们在评论中讨论的那样,一个使用的例子getFooBar http://pastebin.com/UGNvgjEZ造成混乱,因为它不需要任何参数来推断T
从。某些编译器抛出一个错误 http://ideone.com/3CqrAJ在无上下文的调用中getFooBar()
而其他人没问题 http://ideone.com/YuKQLq. I thought不一致的编译错误 - 以及调用的事实FooBar.<?>getFooBar()
是非法的 - 验证了我的观点,但这些结果证明是转移注意力的。
根据@MiserableVariable的回答,我把一个新的例子 http://ideone.com/V1VED9它使用带有参数的通用方法来消除混乱。假设我们有接口Foo
and Bar
和一个实现FooBarImpl
:
interface Foo { }
interface Bar { }
static class FooBarImpl implements Foo, Bar { }
我们还有一个简单的容器类,它包装了某种类型的实例,实现Foo
and Bar
。它声明了一个愚蠢的静态方法unwrap
这需要一个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
into unwrap
。这表明我之前的断言是错误的,因为调用站点不知道什么T
是 - 只是它是边界内的某种类型extends Foo & Bar
.
FooBarContainer.unwrap(unknownFooBarContainer); // T is a wildcard capture, ?
正如我所指出的,打电话unwrap
使用通配符是非法的:
FooBarContainer.<?>unwrap(unknownFooBarContainer); // compiler error
我只能猜测这是因为通配符捕获永远无法相互匹配 -?
调用站点提供的参数不明确,无法说它应该专门匹配类型中的通配符unknownFooBarContainer
.
因此,这是 OP 询问的语法的用例。呼唤unwrap
on unknownFooBarContainer
返回类型的引用? extends Foo & Bar
。我们可以将该引用分配给Foo
or 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);
这只是一个相当模糊的用例。允许这样的语法会产生相当广泛的影响,无论好坏。它会在不需要的地方为滥用开辟空间,并且完全可以理解为什么语言设计者没有实现这样的事情。但我仍然觉得思考起来很有趣。
Note- 从 JDK 10 开始,有var
保留类型名称,这使得这成为可能:
var fooBar = FooBarContainer.unwrap(unknownFooBarContainer);
变量fooBar
被推断为具有实现两者的类型Foo
and Bar
并且不能在源代码中明确表示。
关于堆污染的注意事项
(主要是为了@MiserableVariable https://stackoverflow.com/questions/14464226/how-to-reference-a-generic-return-type-with-multiple-bounds#comment20233991_14469627)以下是不安全方法的演练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
}
在这里,当新Foo2
被投射到T
,它是“未经检查的”,意味着由于类型擦除,运行时不知道它应该失败,即使在这种情况下它应该失败,因为T
was 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
}
现在它不会在调用站点爆炸。一段时间后它会爆炸,当内容foo1List
习惯了。这就是堆污染变得更难调试的原因,因为异常堆栈跟踪不会向您指出实际问题。
当调用者本身处于通用范围内时,情况会变得更加复杂。想象一下而不是得到一个List<Foo1>
我们得到了List<T>
,将其放入Map<K, List<T>>
并将其返回到另一个方法。我希望你能明白我的想法。