我最近看到可以声明一个也受接口限制的返回类型。考虑以下类和接口:
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真的发明了这么好的功能,但忘记了想要引用它吗?
答案 0 :(得分:8)
有些情况下,调用者可以使用返回值的被调用方法,而不知道具体类型。甚至可能根本不存在这种类型,它只是一个代理:
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());
}
}
FooBar1
和FooBar2
都实施Foo
和Bar
。在main
中,可以将对getFooBar1
和getFooBar2
的调用分配给变量,但没有充分的理由让它知道恕我直言。
但getFooBar
是一个有趣的案例,它使用代理。实际上,它可能是实现两个接口的对象的 only 实例。另一种方法(此处为show
)可以与类型更安全的临时方式一起使用,但如果没有问题中描述的FooBarWrapper
黑客,则无法将其分配给变量。甚至不可能创建通用包装器,不允许使用class Wrapper<T extends U & V>
。
唯一的麻烦似乎是定义语法,其他类型检查机制似乎已经到位,至少在Oracle javac 1.7.0中。
答案 1 :(得分:4)
总有一些替代方法可以避免使用这种语法。我想不出一个完全必要的例子。但是,我可以想到一个特定情况的用例,这种语法可以方便地使用,虽然我自己甚至没有使用它。我知道它不是最好的例子,但它可以达到目的。
最近我一直在开发用户界面。在这个应用程序中,我使用库来管理我的GUI元素。除了库的功能之外,我还创建了一个自定义界面,在我的应用程序中定义了一个View,它具有特定类型数据的输入,比如输入坐标。该界面看起来像:
public interface CoordinateView extends View
{
Coordinate getCoordinate();
//Maybe more stuff
}
我的应用程序中有几个实现此接口的窗口。现在让我们说,由于某种原因,我想在模型中存储在窗口中提交的最后一个坐标,然后关闭窗口。为此,我可以将一个处理程序附加到提交表单的窗口按钮,当用户关闭Window时,处理程序将被触发。我可以通过在每个窗口中匿名添加处理程序来实现这一点,例如:
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();
}
});
}
}
然而,这种设计对我来说并不理想,它不够模块化。考虑到我有相当数量的窗口具有这种行为,改变它可能会变得相当乏味。所以我宁愿在类中提取匿名方法,以便更容易更改和维护。但问题是destroy()方法没有在任何接口中定义,只是窗口的一部分,而getCoordinate()方法是在我定义的接口中定义的。
在这种情况下,我可以使用多个边界,如下所示:
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();
}
}
然后,窗口中的代码现在将是:
public MyWindow extends Window implements CoordinateView, OtherInterface
{
private Button submitButton;
public MyWindow()
{
super();
//Create all the elements
submitButton.addClickHandler(new MyController<MyWindow>(this));
}
}
请注意,行为将保持不变,代码只是一如既往的凝聚力。它只是更模块化,但它不需要创建一个额外的接口,以便能够正确地提取它。
或者,我可以定义一个扩展CoordinateView
的附加接口,并定义一个关闭窗口的方法。
public interface CoordinateWindow extends CoordinateView
{
void destroy();
}
让窗口实现这个更具体的界面,而不是在提取的控制器中不必要地使用泛型参数:
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();
}
}
对于某些人来说,这种方法可以看作比以前更清晰,甚至更可重复使用,因为现在可以将其添加到指定层次结构之外的其他“窗口”。就个人而言,我也更喜欢这种方法。但是,它可能会导致更多的编码,因为必须仅为了访问所需方法而定义新接口。
总之,虽然我个人不推荐它,但我认为使用具有多个边界的泛型类型可以帮助耦合定义,同时减少代码量。
答案 2 :(得分:-2)
不确定Eclipse正在为您做什么,但上面的大部分代码都没有接近编译....
我做了相应的更改以尽可能地进行编译,这是我得到的:
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