java 如何引用具有多个边界的通用返回类型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14464226/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to reference a generic return type with multiple bounds
提问by Rafael T
I have recently seen that one can declare a return type that is also bounded by an interface. Consider the following class and interface:
我最近看到可以声明一个也受接口限制的返回类型。考虑以下类和接口:
public class Foo {
public String getFoo() { ... }
}
public interface Bar {
public void setBar(String bar);
}
I can declare a return type like this:
我可以像这样声明一个返回类型:
public class FooBar {
public static <T extends Foo & Bar> T getFooBar() {
//some implementation that returns a Foo object,
//which is forced to implement Bar
}
}
If I call that method from somewhere, my IDE is telling me that the return type has the method String getFoo()
as well as setBar(String)
, but only If I point a dot behind the Functionlike this:
如果我从某处调用该方法,我的 IDE 会告诉我返回类型具有该方法String getFoo()
以及setBar(String)
,但前提是我像这样在Function后面点一个点:
FooBar.getFooBar(). // here the IDE is showing the available methods.
Is there a way to get a reference to such an Object? I mean, if I would do something like this:
有没有办法获得对这样一个对象的引用?我的意思是,如果我会做这样的事情:
//bar only has the method setBar(String)
Bar bar = FooBar.getFooBar();
//foo only has the getFoo():String method
Foo foo = FooBar.getFooBar();
I would like to have a reference like this (pseudo code):
我想有一个这样的参考(伪代码):
<T extents Foo & Bar> fooBar = FooBar.getFooBar();
//or maybe
Bar bar = FooBar.getFooBar();
//or else maybe
Foo&Bar bar = FooBar.getFooBar();
Is this possible somehow in Java, or am I only able to declare return types like this? I think Java has to type it also, somehow. I'd prefer not to resort to a wrapper like this, as it feels like cheating:
这在 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();
}
}
Did Java really invent such a nice feature, but forget that one would like to have a reference to it?
Java 真的发明了这么好的特性,但忘记了人们想要引用它吗?
采纳答案by Paul Bellora
While the type parameters of a generic method can be restricted by bounds, such as extends Foo & Bar
, they are ultimately decided by the caller. When you call getFooBar()
, the call site already knows what T
is being resolved to. Often, these type parameters will be inferredby the compiler, which is why you don't usually need to specify them, like this:
虽然泛型方法的类型参数可以受边界限制,例如extends Foo & Bar
,但它们最终由调用者决定。当您调用 时getFooBar()
,调用站点已经知道T
要解析的内容。通常,编译器会推断这些类型参数,这就是为什么通常不需要指定它们的原因,如下所示:
FooBar.<FooAndBar>getFooBar();
But even when T
is inferred to be FooAndBar
, that's really whats happening behind the scenes.
但即使T
推断为FooAndBar
,这也确实是幕后发生的事情。
So, to answer your question, such a syntax like this:
因此,要回答您的问题,请使用如下语法:
Foo&Bar bothFooAndBar = FooBar.getFooBar();
Would never be useful in practice. The reason is that the caller must already knowwhat T
is. Either T
is some concrete type:
在实践中永远不会有用。原因是调用者必须已经知道是什么T
。要么T
是某种具体类型:
FooAndBar bothFooAndBar = FooBar.<FooAndBar>getFooBar(); // T is FooAndBar
Or, T
is an unresolved type parameter, and we're in its scope:
或者,T
是一个未解析的类型参数,我们在它的范围内:
<U extends Foo & Bar> void someGenericMethod() {
U bothFooAndBar = FooBar.<U>getFooBar(); // T is U
}
Another example of that:
另一个例子:
class SomeGenericClass<V extends Foo & Bar> {
void someMethod() {
V bothFooAndBar = FooBar.<V>getFooBar(); // T is V
}
}
Technically, that wraps up the answer. But I'd also like to point out that your example method getFooBar
is inherently unsafe. Remember that the caller decides what T
gets to be, not the method. Since getFooBar
doesn't take any parameters related to T
, and because of type erasure, its only options would be to return null
or to "lie" by making an unchecked cast, risking heap pollution. A typical workaround would be for getFooBar
to take a Class<T>
argument, or else a FooFactory<T>
for example.
从技术上讲,这就是答案。但我还想指出,您的示例方法getFooBar
本质上是不安全的。请记住,调用者决定要做什么T
,而不是方法。由于getFooBar
不接受与 相关的任何参数T
,并且由于类型擦除,它唯一的选择是null
通过进行未经检查的强制转换来返回或“撒谎”,冒着堆污染的风险。一个典型的解决方法是getFooBar
接受一个Class<T>
论点,或者一个FooFactory<T>
例子。
Update
更新
It turns out I was wrong when I asserted that the caller of getFooBar
must always know what T
is. As @MiserableVariable points out, there are some situations where the type argument of a generic method is inferred to be a wildcard capture, rather than a concrete type or type variable. See his answerfor a great example of a getFooBar
implementation that uses a proxy to drive home his point that T
is unknown.
事实证明,当我断言 的调用者getFooBar
必须始终知道是什么时我错了T
。正如@MiserableVariable 指出的那样,在某些情况下,泛型方法的类型参数被推断为通配符捕获,而不是具体的类型或类型变量。请参阅他的回答,getFooBar
了解使用代理将他的T
未知观点带回家的实现的一个很好的示例。
As we discussed in the comments, an example using getFooBar
created confusion because it takes no arguments to infer T
from. Certain compilers throw an erroron a contextless call to getFooBar()
while others are fine with it. I thoughtthat the inconsistent compile errors - along with the fact that calling FooBar.<?>getFooBar()
is illegal - validated my point, but these turned out to be red herrings.
正如我们在评论中所讨论的那样,使用的示例getFooBar
造成了混淆,因为它不需要推断参数T
。某些编译器在无上下文调用时抛出错误,getFooBar()
而其他编译器则可以。我认为不一致的编译错误 - 以及调用FooBar.<?>getFooBar()
是非法的这一事实- 验证了我的观点,但结果证明这些都是红鲱鱼。
Based on @MiserableVariable's answer, I put together an new examplethat uses a generic method with an argument, to remove the confusion. Assume we have interfaces Foo
and Bar
and an implementation FooBarImpl
:
根据@MiserableVariable 的回答,我整理了一个使用带有参数的泛型方法的新示例,以消除混淆。假设我们有接口Foo
和Bar
和实施FooBarImpl
:
interface Foo { }
interface Bar { }
static class FooBarImpl implements Foo, Bar { }
We also have a simple container class that wraps an instance of some type implementing Foo
and Bar
. It declares a silly static method unwrap
that takes a FooBarContainer
and returns its referent:
我们还有一个简单的容器类,它包装了实现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();
}
}
Now let's say we have a wildcard parameterized type of FooBarContainer
:
现在假设我们有一个通配符参数化类型FooBarContainer
:
FooBarContainer<?> unknownFooBarContainer = ...;
We're allowed to pass unknownFooBarContainer
into unwrap
. This shows my earlier assertion was wrong, because the call site doesn't know what T
is - only that it is some type within the bounds extends Foo & Bar
.
我们被允许unknownFooBarContainer
进入unwrap
. 这表明我之前的断言是错误的,因为调用站点不知道是什么T
——只知道它是 bounds 内的某种类型extends Foo & Bar
。
FooBarContainer.unwrap(unknownFooBarContainer); // T is a wildcard capture, ?
As I noted, calling unwrap
with a wildcard is illegal:
正如我所指出的,unwrap
使用通配符调用是非法的:
FooBarContainer.<?>unwrap(unknownFooBarContainer); // compiler error
I can only guess that this is because wildcard captures can never match each other - the ?
argument provided at the call site is ambiguous, with no way of saying that it should specifically match the wildcard in the type of unknownFooBarContainer
.
我只能猜测这是因为通配符捕获永远不能相互匹配 -?
在调用站点提供的参数是模棱两可的,没有办法说它应该专门匹配unknownFooBarContainer
.
So, here's the use case for the syntax the OP is asking about. Calling unwrap
on unknownFooBarContainer
returns a reference of type ? extends Foo & Bar
. We can assign that reference to Foo
or Bar
, but not both:
所以,这是 OP 询问的语法的用例。调用unwrap
的unknownFooBarContainer
返回类型的引用? extends Foo & Bar
。我们可以将该引用分配给Foo
或Bar
,但不能同时分配给两者:
Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);
Bar bar = FooBarContainer.unwrap(unknownFooBarContainer);
If for some reason unwrap
were expensive and we only wanted to call it once, we would be forced to cast:
如果由于某种原因unwrap
很昂贵并且我们只想调用一次,我们将被迫强制转换:
Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);
Bar bar = (Bar)foo;
So this is where the hypothetical syntax would come in handy:
所以这是假设语法派上用场的地方:
Foo&Bar fooBar = FooBarContainer.unwrap(unknownFooBarContainer);
This is just one fairly obscure use case. There would be pretty far-ranging implications for allowing such a syntax, both good and bad. It would open up room for abuse where it wasn't needed, and it's completely understandable why the language designers didn't implement such a thing. But I still think it's interesting to think about.
这只是一个相当模糊的用例。允许这样一种语法,无论好坏,都会产生相当广泛的影响。它会在不需要的地方为滥用开辟空间,并且完全可以理解为什么语言设计者没有实现这样的东西。但我还是觉得想想还是很有趣的。
A note about heap pollution
关于堆污染的说明
(Mostly for @MiserableVariable) Here's a walkthrough of how an unsafe method like getFooBar
causes heap pollution, and its implications. Given the following interface and implementations:
(主要用于 @MiserableVariable)这里有一个不安全的方法如何getFooBar
导致堆污染及其影响的演练。鉴于以下接口和实现:
interface Foo { }
static class Foo1 implements Foo {
public void foo1Method() { }
}
static class Foo2 implements Foo { }
Let's implement an unsafe method getFoo
, similar to getFooBar
but simplified for this example:
让我们实现一个不安全的方法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
}
Here, when the new Foo2
is cast to T
, it is "unchecked", meaning because of type erasure the runtime doesn't know it should fail, even though it should in this case since T
was Foo1
. Instead, the heap is "polluted", meaning references are pointing to objects they shouldn't have been allowed to.
在这里,当 newFoo2
被T
强制转换为 时,它是“未经检查的”,这意味着由于类型擦除,运行时不知道它应该失败,即使在这种情况下它应该失败,因为T
是Foo1
。相反,堆被“污染”了,这意味着引用指向他们不应该被允许的对象。
The failure happens after the method returns, when the Foo2
instance tries to get assigned to the foo1
reference, which has the reifiable type Foo1
.
失败发生在方法返回之后,当Foo2
实例尝试分配给foo1
具有 reifiable 类型的引用时Foo1
。
You're probably thinking, "Okay so it blew up at the call site instead of the method, big deal." But it can easily get more complicated when more generics are involved. For example:
你可能会想,“好吧,它在调用站点而不是方法上爆炸了,大不了。” 但是当涉及更多泛型时,它很容易变得更加复杂。例如:
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
}
Now it doesn't blow up at the call site. It blows up sometime later when the contents of foo1List
get used. This is how heap pollution gets harder to debug, because the exception stacktrace doesn't point you to the actual problem.
现在它不会在呼叫站点爆炸。当内容foo1List
被使用时,它会在一段时间后爆炸。这就是堆污染变得更难调试的原因,因为异常堆栈跟踪并没有将您指向实际问题。
It gets even more complicated when the caller is in generic scope itself. Imagine instead of getting a List<Foo1>
we're getting a List<T>
, putting it in a Map<K, List<T>>
and returning it to yet another method. You get the idea I hope.
当调用者在泛型范围内时,它会变得更加复杂。想象一下,List<Foo1>
我们不是得到 a而是得到 a List<T>
,而是将其放入 aMap<K, List<T>>
并将其返回给另一个方法。你明白我希望的想法。
回答by Miserable Variable
There are cases where a called method returning a value can be used by the caller withoutknowing the concrete type. It is even likely that such a type does not exist at all, it is only a proxy:
在某些情况下,调用者可以在不知道具体类型的情况下使用返回值的被调用方法。甚至可能根本不存在这样的类型,它只是一个代理:
import java.lang.reflect.*;
interface Foo {}
interface Bar {}
class FooBar1 implements Foo, Bar {public String toString() { return "FooBar1"; }}
class FooBar2 implements Foo, Bar {public String toString() { return "FooBar2"; }}
class FooBar {
static <T extends Foo & Bar> T getFooBar1() { return (T) new FooBar1(); }
static <T extends Foo & Bar> T getFooBar2() { return (T) new FooBar2(); }
static <T extends Foo & Bar> T getFooBar() {
return (T)
Proxy.newProxyInstance(
Foo.class.getClassLoader(),
new Class[] { Foo.class, Bar.class },
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
return "PROXY!!!";}});
}
static <U extends Foo & Bar> void show(U u) { System.out.println(u); }
public static void main(String[] args) {
show(getFooBar1());
show(getFooBar2());
show(getFooBar());
}
}
Both FooBar1
and FooBar2
implement Foo
and Bar
. In main
, the calls to getFooBar1
and getFooBar2
can be assigned to a variable, though there isn't a strong reason for it to know IMHO.
双方FooBar1
并FooBar2
落实Foo
和Bar
。在 中main
,对getFooBar1
和的调用getFooBar2
可以分配给一个变量,尽管没有充分的理由让它知道恕我直言。
But getFooBar
is the interesting case, which uses a proxy. In practice, it may be the onlyinstance of a an object that implements the two interfaces. A different method (show
here) can be used with a temporary in a type-safer manner, but it cannot be assigned to a variable without the FooBarWrapper
hack described in the question. It is not even possible to create a generic wrapper, class Wrapper<T extends U & V>
is not allowed.
但getFooBar
有趣的是使用代理的情况。实际上,它可能是实现这两个接口的对象的唯一实例。show
可以以类型更安全的方式将不同的方法(此处)与临时方法一起使用,但如果没有FooBarWrapper
问题中描述的hack ,则不能将其分配给变量。甚至不可能创建通用包装器,这class Wrapper<T extends U & V>
是不允许的。
The only trouble seems be defining a syntax, other type checking mechanisms seem to be in place, at least in Oracle javac 1.7.0.
唯一的麻烦似乎是定义语法,其他类型检查机制似乎已经到位,至少在 Oracle javac 1.7.0 中。
回答by Sednus
Like @Paul Bellora mentioned in his answer, the type get resolved by the caller, since essentially it will now what it is calling. I would just like to add to his answer with an use case where I think the usage of the syntax could be of benefit.
就像@Paul Bellora 在他的回答中提到的那样,类型由调用者解析,因为基本上它现在将调用它。我只想用一个用例添加到他的答案中,我认为语法的使用可能是有益的。
There are always alternatives that avoid using such syntax. I cannot think of a single example that this is utterly necessary. However I can think of a use case of a particular situation that this syntax could be used conveniently, although I didn't even used it myself. I know its not the best example out there but it can get to the point.
总有替代方法可以避免使用此类语法。我想不出一个例子说明这是完全必要的。但是我可以想到一个特定情况的用例,这个语法可以方便地使用,尽管我自己没有使用它。我知道它不是最好的例子,但它可以切入正题。
Case
案件
Recently I've been working in the development of an user interface. In this application I use a library to manage my GUI elements. In addition to the features of the library, I created a custom interface that defines a View in my application that has inputs for a specific type of data, lets say, input of coordinates. That interface would look like:
最近我一直在开发用户界面。在这个应用程序中,我使用一个库来管理我的 GUI 元素。除了库的功能之外,我还创建了一个自定义界面,该界面在我的应用程序中定义了一个视图,该视图具有特定类型数据的输入,比如坐标输入。该界面将如下所示:
public interface CoordinateView extends View
{
Coordinate getCoordinate();
//Maybe more stuff
}
I have several windows across my application that implement this interface. Now lets say that for some reason I want to store in a model the last coordinate submitted in a window and close the window right after. For this I can attach a handler to the window button that submits the form, the handler will get triggered when the user closes the Window. I could achieve that by simply adding the handler anonymously in every window, like:
我的应用程序中有几个窗口实现了这个接口。现在假设出于某种原因,我想将窗口中提交的最后一个坐标存储在模型中,然后立即关闭窗口。为此,我可以将一个处理程序附加到提交表单的窗口按钮上,当用户关闭窗口时,该处理程序将被触发。我可以通过简单地在每个窗口中匿名添加处理程序来实现这一点,例如:
public MyWindow extends Window implements CoordinateView, OtherInterface
{
private Button submitButton;
public MyWindow()
{
super();
//Create all the elements
submitButton.addClickHandler(
new ClickHandler()
{
@Override
onCLick(ClickEvent e)
{
getModel().add(getCoordinate());
destroy();
}
});
}
}
However, this design is not desirable for me, it is not modular enough. Considering I have a decent amount of windows with this behavior, changing it could get rather tedious. So I rather extract the anonymous method in a class so that it would be easier to change and maintain. But the problem is that the destroy() method is not defined in any interface, is just part of window and the getCoordinate() method is defined in the interface I defined.
但是,这种设计对我来说并不理想,它不够模块化。考虑到我有相当数量的窗口具有这种行为,改变它可能会变得相当乏味。所以我宁愿把匿名方法提取到一个类中,这样更容易修改和维护。但问题是destroy() 方法没有在任何接口中定义,只是window 的一部分,而getCoordinate() 方法是在我定义的接口中定义的。
Usage
用法
In this case I could use multiple bounds like the following:
在这种情况下,我可以使用多个边界,如下所示:
public class MyController <T extends Window & CoordinateView> implements ClickHandler
{
private T windowWithCoordinates;
public MyController (T window)
{
windowWithCoordinates = window;
}
@Override
onClick(ClickEvent e)
{
getModel().add(windowWithCoordinates.getCoordinate());
windowWithCoordinate.destroy();
}
}
Then the code in the windows will now be:
然后窗口中的代码现在将是:
public MyWindow extends Window implements CoordinateView, OtherInterface
{
private Button submitButton;
public MyWindow()
{
super();
//Create all the elements
submitButton.addClickHandler(new MyController<MyWindow>(this));
}
}
Notice that the behaviour will remain the same, the code is just a cohesive as it used to be. Its only more modular, but it didn't required the creation of an additional interface to be able to extract it properly.
请注意,行为将保持不变,代码只是像以前一样具有凝聚力。它只是更加模块化,但不需要创建额外的接口来正确提取它。
Alternative
选择
Alternatively, I could have defined an additional interface extending CoordinateView
and define a method to close the window.
或者,我可以定义一个额外的接口扩展CoordinateView
并定义一个方法来关闭窗口。
public interface CoordinateWindow extends CoordinateView
{
void destroy();
}
Having the window implement this more specific interface instead of making unnecessary use of generic parameters in the extracted controller:
让窗口实现这个更具体的接口,而不是在提取的控制器中不必要地使用通用参数:
public class MyController implements ClickHandler
{
private CoordinateWindow windowWithCoordinates;
public MyController (CoordinateWindow window)
{
windowWithCoordinates = window;
}
@Override
onClick(ClickEvent e)
{
getModel().add(windowWithCoordinates.getCoordinate());
windowWithCoordinate.destroy();
}
}
public MyWindow extends Window implements CoordinateWindow
{
private Button submitButton;
public MyWindow()
{
super();
//Create all the elements
submitButton.addClickHandler(new MyController(this));
}
@Override
void destroy()
{
this.destroy();
}
}
This approach for some can be seen as much cleaner than the previous and even more reusable since now it could be added to other "windows" outside of the specified hierarchy. Personally, I prefer this approach as well. However, it may result in a little more coding since a new interface has to be defined just for the sake of getting a access to a desired method.
对于某些人来说,这种方法比以前的方法更简洁,甚至更可重用,因为现在它可以添加到指定层次结构之外的其他“窗口”。就我个人而言,我也更喜欢这种方法。然而,它可能会导致更多的编码,因为必须定义一个新的接口只是为了获得对所需方法的访问。
In conclusion, although I personally don't recommend it I think using generic types with multiple bounds could help in coupling definitions while reducing the amount of code.
总之,虽然我个人不推荐它,但我认为使用具有多个边界的泛型类型有助于在减少代码量的同时耦合定义。
回答by jco.owens
Not sure what Eclipse is doing for you, but most of the code above does not come close to compiling....
不确定 Eclipse 为你做什么,但上面的大部分代码都没有接近编译......
I made the appropriate changes to get it to compile as much as possible and here is what I get:
我进行了适当的更改以使其尽可能多地编译,这是我得到的:
public class Foo
{
public String getFoo() { return ""; } // must have a body
}
public interface Bar // no ()
{
public void setBar(String bar);
}
public class FooBar<T>
{
public static <T extends Foo & Bar> T getFooBar()
{
return null;
}
}
public class FB
{
private FooBar<Object> fb = new FooBar<Object>();
public static void main(String args[])
{
new FB();
}
public FB()
{
System.out.println(fb.getFooBar());
}
}
FB.java:12: type parameters of <T>T cannot be determined; no unique maximal instance exists for type variable T with upper bounds java.lang.Object,Foo,Bar
System.out.println(fb.getFooBar());
^
1 error