如何引用具有多个边界的泛型返回类型

时间:2013-01-22 17:14:44

标签: java generics interface multiple-inheritance

我最近看到可以声明一个也受接口限制的返回类型。考虑以下类和接口:

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真的发明了这么好的功能,但忘记了想要引用它吗?

3 个答案:

答案 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());      
    }

}

FooBar1FooBar2都实施FooBar。在main中,可以将对getFooBar1getFooBar2的调用分配给变量,但没有充分的理由让它知道恕我直言。

getFooBar是一个有趣的案例,它使用代理。实际上,它可能是实现两个接口的对象的 only 实例。另一种方法(此处为show)可以与类型更安全的临时方式一起使用,但如果没有问题中描述的FooBarWrapper黑客,则无法将其分配给变量。甚至不可能创建通用包装器,不允许使用class Wrapper<T extends U & V>

唯一的麻烦似乎是定义语法,其他类型检查机制似乎已经到位,至少在Oracle javac 1.7.0中。

答案 1 :(得分:4)

像@Paul Bellora在他的回答中提到的那样,类型由调用者解决,因为它现在基本上它将调用它。我想在一个用例中添加他的答案,我认为语法的使用可能是有益的。

总有一些替代方法可以避免使用这种语法。我想不出一个完全必要的例子。但是,我可以想到一个特定情况的用例,这种语法可以方便地使用,虽然我自己甚至没有使用它。我知道它不是最好的例子,但它可以达到目的。

案例

最近我一直在开发用户界面。在这个应用程序中,我使用库来管理我的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