我最近看到可以声明一个也受接口(interface)限制的返回类型。考虑以下类和接口(interface):
public class Foo {
public String getFoo() { ... }
}
public interface Bar {
public void setBar(String bar);
}
我可以声明一个这样的返回类型:
public class FooBar {
public static <T extends Foo & Bar> T getFooBar() {
//some implementation that returns a Foo object,
//which is forced to implement Bar
}
}
如果我从某处调用该方法,我的 IDE 会告诉我返回类型具有方法
String getFoo()
以及 setBar(String)
,但前提是我像这样在函数后面点一个点:FooBar.getFooBar(). // here the IDE is showing the available methods.
有没有办法获得对这样一个对象的引用?我的意思是,如果我会做这样的事情:
//bar only has the method setBar(String)
Bar bar = FooBar.getFooBar();
//foo only has the getFoo():String method
Foo foo = FooBar.getFooBar();
我想有一个这样的引用(伪代码):
<T extents Foo & Bar> fooBar = FooBar.getFooBar();
//or maybe
$1Bar bar = FooBar.getFooBar();
//or else maybe
Foo&Bar bar = FooBar.getFooBar();
这在 Java 中以某种方式是可能的,还是我只能像这样声明返回类型?我认为 Java 也必须以某种方式输入它。我不想使用这样的包装器,因为它感觉像是在作弊:
public class FooBarWrapper<T extends Foo&Bar> extends Foo implements Bar {
public T foobar;
public TestClass(T val){
foobar = val;
}
@Override
public void setBar(String bar) {
foobar.setBar(bar);
}
@Override
public String getFoo() {
return foobar.getFoo();
}
}
Java 真的发明了这么好的特性,但忘记了人们想要引用它吗?
最佳答案
而泛型方法的类型参数可以通过边界限制,例如 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
相关的参数,并且因为 type erasure ,它唯一的选择是返回 null
或通过未经检查的类型转换“撒谎”,冒风险 heap pollution .典型的解决方法是 getFooBar
拿个Class<T>
参数,否则是 FooFactory<T>
例如。更新
事实证明,当我断言
getFooBar
的调用者时我错了。必须始终知道什么 T
是。正如@MiserableVariable 指出的那样,在某些情况下,泛型方法的类型参数被推断为通配符捕获,而不是具体的类型或类型变量。 见 his answer一个很好的例子 getFooBar
使用代理来实现他的观点 T
未知。 正如我们在评论中所讨论的,an example using
getFooBar
造成困惑,因为它不需要参数来推断 T
从。某些编译器 throw an error在对 getFooBar()
的无上下文调用中而其他人are fine with it .我认为不一致的编译错误 - 以及调用 FooBar.<?>getFooBar()
的事实。是非法的 - 验证了我的观点,但这些结果是红鲱鱼。根据@MiserableVariable 的回答,我整理了 an new example使用带有参数的泛型方法来消除混淆。假设我们有接口(interface)
Foo
和 Bar
和一个实现 FooBarImpl
:interface Foo { }
interface Bar { }
static class FooBarImpl implements Foo, Bar { }
我们还有一个简单的容器类,它包装了实现
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 询问的语法的用例。调用
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);
这只是一个相当模糊的用例。允许这样的语法,无论好坏,都会产生相当广泛的影响。它会在不需要的地方为滥用开辟空间,完全可以理解为什么语言设计者没有实现这样的东西。但我还是觉得想想还是很有趣的。
关于堆污染的说明
( Mostly for @MiserableVariable ) 以下是对像
getFooBar
这样的不安全方法的演练导致堆污染及其影响。鉴于以下接口(interface)和实现: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
.相反,堆被“污染”了,这意味着引用指向他们不应该被允许的对象。失败发生在方法返回之后,当
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>>
并将其返回到另一种方法。你明白我希望的想法。
关于java - 如何引用具有多个边界的通用返回类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14464226/