web-dev-qa-db-ja.com

複数の境界を持つジェネリック戻り値型を参照する方法

最近、インターフェースによってもバインドされる戻り型を宣言できることがわかりました。次のクラスとインターフェースについて考えてみます。

_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)があることを伝えていますが、私はFunctionの後ろにドットを次のように指しています:

_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は本当にそのような素晴らしい機能を発明しましたが、それへの参照が必要であることを忘れていませんか?

40
Rafael T

値を返す呼び出されたメソッドが、具体的な型を知っている呼び出し元withoutで使用できる場合があります。そのようなタイプはまったく存在しない可能性もあり、それは単なるプロキシです。

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への呼び出しを変数に割り当てることができますが、IMHOを認識する強力な理由はありません。

しかしgetFooBarは興味深いケースです、これはプロキシを使用します。実際には、2つのインターフェースを実装するオブジェクトのonlyインスタンスである場合があります。別のメソッド(ここではshow)をタイプセーフな方法で一時的に使用できますが、質問で説明されているFooBarWrapperハックなしで変数に割り当てることはできません。ジェネリックラッパーを作成することもできません。class Wrapper<T extends U & V>は使用できません。

唯一の問題は構文を定義することであるように思われ、少なくともOracle javac 1.7.0では他の型チェックメカニズムが適切に機能しているようです。

8

@Paul Belloraが彼の回答で述べたように、型は呼び出し元によって解決されます。基本的には型が呼び出されているからです。私は構文の使用が有益であると思うユースケースで彼の答えに加えたいと思います。

このような構文の使用を回避する代替手段は常に存在します。これが完全に必要であるという単一の例を考えることはできません。しかし、私自身は使用していませんが、この構文が便利に使用できる特定の状況のユースケースを考えることができます。私はそれがそこにある最良の例ではないことを知っていますが、要点を述べることができます。

場合

最近、ユーザーインターフェイスの開発に取り組んでいます。このアプリケーションでは、ライブラリを使用してGUI要素を管理します。ライブラリの機能に加えて、特定のタイプのデータ(たとえば、座標の入力)の入力を持つアプリケーションのビューを定義するカスタムインターフェイスを作成しました。そのインターフェースは次のようになります。

public interface CoordinateView extends View
{
    Coordinate getCoordinate();
    //Maybe more stuff
} 


このインターフェイスを実装するアプリケーション全体でいくつかのウィンドウがあります。なんらかの理由で、ウィンドウに送信された最後の座標をモデルに保存し、その直後にウィンドウを閉じたいとしましょう。このため、フォームを送信するウィンドウボタンにハンドラーをアタッチできます。ユーザーがウィンドウを閉じると、ハンドラーがトリガーされます。次のように、すべてのウィンドウにハンドラーを匿名で追加するだけでそれを実現できます。

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

一部の人にとっては、このアプローチは以前のものよりもはるかにクリーンであり、指定された階層外の他の「ウィンドウ」に追加できるようになったため、さらに再利用可能と見なすことができます。個人的には、私もこのアプローチを好みます。ただし、目的のメソッドにアクセスするためだけに新しいインターフェースを定義する必要があるため、コーディングが少し増える可能性があります。

結論として、私は個人的にはお勧めしませんが、複数の境界を持つジェネリック型を使用すると、コードの量を減らしながら定義を結合するのに役立つと思います。

4
Sednus