虽然泛型方法的类型参数可以受边界限制,例如,但它们最终由调用方决定。当您呼叫 时,呼叫站点已经知道要解析为什么。通常,编译器会推断这些类型参数,这就是为什么您通常不需要指定它们的原因,如下所示: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>
更新
事实证明,当我断言的呼叫者必须始终知道是什么时,我错了。正如@MiserableVariable所指出的,在某些情况下,泛型方法的类型参数被推断为通配符捕获,而不是具体的类型或类型变量。查看他的答案,了解getFooBar
实现的一个很好的例子,该示例使用代理来说明他的观点,即T
是未知的。getFooBar
T
正如我们在评论中讨论的那样,使用getFooBar
的示例造成了混乱,因为它不需要参数来推断。某些编译器在无上下文调用时会引发错误,而其他编译器则对此感到满意。我认为不一致的编译错误 - 以及调用是非法的事实 - 验证了我的观点,但事实证明这些是红鲱鱼。T
getFooBar()
FooBar.<?>getFooBar()
基于@MiserableVariable的答案,我整理了一个新示例,该示例使用带有参数的通用方法,以消除混淆。假设我们有接口和一个实现:Foo
Bar
FooBarImpl
interface Foo { }
interface Bar { }
static class FooBarImpl implements Foo, Bar { }
我们还有一个简单的容器类,它包装了某种类型的实例实现和 .它声明了一个愚蠢的静态方法,该方法采用 a 并返回其引用:Foo
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
unwrap
T
extends Foo & Bar
FooBarContainer.unwrap(unknownFooBarContainer); // T is a wildcard capture, ?
正如我所指出的,使用通配符进行呼叫是非法的:unwrap
FooBarContainer.<?>unwrap(unknownFooBarContainer); // compiler error
我只能猜测这是因为通配符捕获永远无法相互匹配 - 调用站点上提供的参数是模棱两可的,没有办法说它应该专门匹配类型中的通配符。?
unknownFooBarContainer
因此,这是OP所询问的语法的用例。调用 on 将返回类型的引用。我们可以将该引用赋给 或 ,但不能同时分配给两者: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);
这只是一个相当晦涩难懂的用例。允许这种语法(无论是好的还是坏的)将会产生非常深远的影响。这将在不需要的地方为滥用打开空间,并且完全可以理解为什么语言设计人员没有实现这样的事情。但我仍然认为思考起来很有趣。
注意 - 从 JDK 10 开始,存在保留类型名称,这使得这成为可能:var
var fooBar = FooBarContainer.unwrap(unknownFooBarContainer);
该变量被推断为具有一个同时实现和的类型,并且不能在源代码中显式表示。fooBar
Foo
Bar
关于堆污染的说明
(主要用于@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
}
在这里,当新内容被强制转换为 时,它是“未选中的”,这意味着由于类型擦除,运行时不知道它应该失败,即使在这种情况下它应该因为是 。相反,堆是“污染的”,这意味着引用指向它们不应该被允许的对象。Foo2
T
T
Foo1
当实例尝试分配给具有 reifiable 类型 的引用时,该方法返回后,将发生故障。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
当调用方处于通用范围本身时,它会变得更加复杂。想象一下,我们得到的不是 a,而是把它放在 a 中,然后返回到另一种方法。你明白我希望的想法。List<Foo1>
List<T>
Map<K, List<T>>