Javaデリゲート?


194

Java言語には、C#がデリゲートをサポートするのと同様に、デリゲート機能がありますか?


20
@スマあなたが言及した質問がこの質問の1年後に投稿された場​​合、これはどのように重複する可能性がありますか?
Ani

1
Java 8には、デリゲートによく似た機能があります。それはラムダと呼ばれています。
tbodt 2014年

4
@tbodtの方が正確ですが、Java 8にはデリゲートによく似た機能があります。これは、機能インターフェースと呼ばれます。ラムダは、そのようなデリゲートインスタンスを(匿名で)作成する1つの方法です。
nawfal 14

3
:以下の答えは、Java 8は、このスレッドで答えを見る前のJava 8のポストからですstackoverflow.com/questions/20311779/...
マイケル・ロイド・リーMLK

@ nawfal、+ 1、さらに正確に言うと、Java 7以前には、デリゲートに非常によく似た機能があります。プレーンインターフェースと呼ばれます。
Pacerier 2014年

回答:


152

いいえ、違います。

リフレクションを使用して、次に呼び出すことができるMethodオブジェクトを取得することで同じ効果を達成できる場合があります。もう1つの方法は、単一の「invoke」または「execute」メソッドを使用してインターフェイスを作成し、インスタンス化してメソッドを呼び出すことです。興味がある(つまり、匿名の内部クラスを使用する)。

また、この記事は興味深い/役に立つかもしれません:JavaプログラマーがC#Delegates(@ archive.org)を見る


3
インターフェースの唯一のメンバー関数として、invoke()を使用するソリューションは本当に素晴らしいです。
ステファンRolland

1
しかし、私の例を参照してくださいここでは、C#のデリゲートと同等を達成するために、も、Javaの7で、1はどうなるのかの。
ToolmakerSteve 2015年

63

正確に何を意味するかに応じて、戦略パターンを使用して同様の効果(メソッドを渡す)を実現できます。

名前付きメソッドシグネチャを宣言するこのような行の代わりに:

// C#
public delegate void SomeFunction();

インターフェイスを宣言します。

// Java
public interface ISomeBehaviour {
   void SomeFunction();
}

メソッドの具体的な実装については、動作を実装するクラスを定義します。

// Java
public class TypeABehaviour implements ISomeBehaviour {
   public void SomeFunction() {
      // TypeA behaviour
   }
}

public class TypeBBehaviour implements ISomeBehaviour {
   public void SomeFunction() {
      // TypeB behaviour
   }
}

次にSomeFunction、C#にデリゲートがある場合は、ISomeBehaviour代わりに参照を使用します。

// C#
SomeFunction doSomething = SomeMethod;
doSomething();
doSomething = SomeOtherMethod;
doSomething();

// Java
ISomeBehaviour someBehaviour = new TypeABehaviour();
someBehaviour.SomeFunction();
someBehaviour = new TypeBBehaviour();
someBehaviour.SomeFunction();

匿名の内部クラスを使用すると、個別の名前付きクラスを宣言せずに、実際のデリゲート関数のように扱うことができます。

// Java
public void SomeMethod(ISomeBehaviour pSomeBehaviour) {
   ...
}

...

SomeMethod(new ISomeBehaviour() { 
   @Override
   public void SomeFunction() {
      // your implementation
   }
});

これはおそらく、実装が現在のコンテキストに非常に固有であり、再利用してもメリットがない場合にのみ使用する必要があります。

そしてもちろん、Java 8では、これらは基本的にラムダ式になります。

// Java 8
SomeMethod(() -> { /* your implementation */ });

6
+1。これはまともな回避策であり、冗長性は将来の保守性によって補われる可能性があります。
nawfal 2014年

これは素晴らしい...現在プロジェクトの制約が原因でリフレクションを使用できないプロジェクトに取り組んでおり、この回避策はその仕事を美しく仕上げてくれます:)
Jonathan Camarena

Javaには、インターフェースにIプレフィックスの命名規則がありますか?今まで見たことがない
カイルデラニー

36

短編小説:いいえ

前書き

Microsoft Visual J ++開発環境の最新バージョンは、デリゲートまたはバインドされたメソッド参照と呼ばれる言語構造をサポートしています。この構築、および新しいキーワードdelegatemulticast、それをサポートするために導入には、ジャワの一部ではないTMの で指定されているプログラミング言語、Java言語仕様とによって改正仕様は内部クラスに含まJDKTM 1.1ソフトウェアのマニュアル

Javaプログラミング言語にこの構造が含まれることはほとんどありません。Sunは1996年に、実用的なプロトタイプを構築および破棄するまで、それを採用することを慎重に検討しました。私たちの結論は、バインドされたメソッド参照は不必要であり、言語に有害であるということでした。この決定は、Delphi Object Pascalでバインドされたメソッド参照に関する以前の経験を持つBorland Internationalと相談して行われました。

別の設計代替である内部クラスが同等または優れた機能を提供するため、バインドされたメソッド参照は不要であると考えています。特に、内部クラスはユーザーインターフェイスイベント処理の要件を完全にサポートし、少なくともWindows Foundation Classと同じくらい包括的なユーザーインターフェイスAPIの実装に使用されています。

バインドされたメソッド参照は、Javaプログラミング言語のシンプルさとAPIの広範囲に渡るオブジェクト指向の特性を損なうため、有害であると考えています。バインドされたメソッド参照は、言語の構文とスコープ規則に不規則性をもたらします。最後に、追加の異種の参照とメソッドのリンクを効率的に処理するためにVMが必要となるため、VMテクノロジーへの投資が薄れます。


Patrickが リンクした内容にあるように、代わりに内部クラスを使用したいとします。
SCdF 2008

16
素晴らしい記事。私は彼らの「シンプル」の定義が大好きです:Javaは「Javaはコンパイラ/ VMを書くのが簡単でなければならない」のように単純な言語であるように設計されています。たくさん説明します。
Juozas Kontvainis、2012

5
SUNが大きな間違いを犯したと思います。それらは単に機能的パラダイムに納得していないだけで、それだけです。
Stephane Rolland

7
@Juozas:Pythonは明示的に人が簡単に書き込み/読み取りできるように作成されており、ラムダ関数/デリゲートを実装ています
ステファンRolland

5
archive.orgリンクに置き換えられました。また、それは本当に愚かです、Oracle。
Patrick

18

あなたが読んで持っているこれを

デリゲートは、イベントベースのシステムで役立つ構成です。基本的にデリゲートは、指定されたオブジェクトのメソッドディスパッチをエンコードするオブジェクトです。このドキュメントでは、Java内部クラスがこのような問題に対するより一般的なソリューションを提供する方法を示します。

デリゲートとは何ですか?実際には、C ++で使用されるメンバー関数へのポインターに非常に似ています。ただし、デリゲートには、呼び出されるメソッドと共にターゲットオブジェクトが含まれます。理想的には、次のように言えるとよいでしょう。

obj.registerHandler(ano.methodOne);

..そして、メソッドmethodOneは、特定のイベントが受信されたときにanoで呼び出されます。

これはデリゲート構造が実現するものです。

Java内部クラス

Javaは匿名の内部クラスを介してこの機能を提供するため、追加のデリゲートコンストラクトを必要としないと主張されてきました。

obj.registerHandler(new Handler() {
        public void handleIt(Event ev) {
            methodOne(ev);
        }
      } );

一見これは正しいように見えますが、同時に厄介です。多くのイベント処理の例では、デリゲート構文の単純さが非常に魅力的だからです。

一般ハンドラ

ただし、たとえば、一般的な非同期プログラミング環境の一部として、イベントベースのプログラミングがより広範に使用されている場合は、さらに問題が生じます。

このような一般的な状況では、ターゲットメソッドとターゲットオブジェクトインスタンスのみを含めるだけでは不十分です。一般に、イベントハンドラーの登録時にコンテキスト内で決定される他のパラメーターが必要になる場合があります。

このより一般的な状況では、特に最終変数の使用と組み合わせると、Javaアプローチは非常にエレガントなソリューションを提供できます。

void processState(final T1 p1, final T2 dispatch) { 
  final int a1 = someCalculation();

  m_obj.registerHandler(new Handler() {
    public void handleIt(Event ev) {
     dispatch.methodOne(a1, ev, p1);
    }
  } );
}

最終*最終*最終

あなたの注意を得ましたか?

最終的な変数には匿名クラスのメソッド定義内からアクセスできることに注意してください。影響を理解するために、このコードを注意深く検討してください。これは潜在的に非常に強力なテクニックです。たとえば、MiniDOMでハンドラーを登録するときなど、より一般的な状況で効果的に使用できます。

対照的に、Delegateコンストラクトはこのより一般的な要件に対するソリューションを提供しないため、設計の基礎となるイディオムとして拒否する必要があります。


13

私はこの投稿が古いことを知っていますが、Java 8にはラムダと、1つのメソッドのみを持つ任意のインターフェースである関数型インターフェースの概念が追加されています。これらは共に、C#デリゲートと同様の機能を提供します。詳細についてはこちらをご覧ください。またはGoogle Java Lambdasをググってください。 http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html


5

いいえ、ただしプロキシとリフレクションを使用して偽造できます。

  public static class TestClass {
      public String knockKnock() {
          return "who's there?";
      }
  }

  private final TestClass testInstance = new TestClass();

  @Test public void
  can_delegate_a_single_method_interface_to_an_instance() throws Exception {
      Delegator<TestClass, Callable<String>> knockKnockDelegator = Delegator.ofMethod("knockKnock")
                                                                   .of(TestClass.class)
                                                                   .to(Callable.class);
      Callable<String> callable = knockKnockDelegator.delegateTo(testInstance);
      assertThat(callable.call(), is("who's there?"));
  }

このイディオムの良いところは、委任先を作成した時点で、委任先メソッドが存在し、必要な署名があることを確認できることです(ただし、コンパイル時ではありませんが、残念ながら、FindBugsプラグインはここで助けます)、そして安全にそれを使用してさまざまなインスタンスに委任します。

その他のテスト実装については、githubkargコードを参照してください。


2

リフレクションを使用してJavaでコールバック/デリゲートサポートを実装しました。詳細と作業ソースは私のウェブサイトで入手できます

使い方

WithParmsという名前のネストされたクラスを持つCallbackという名前の基本クラスがあります。コールバックを必要とするAPIは、パラメータとしてCallbackオブジェクトを受け取り、必要に応じて、メソッド変数としてCallback.WithParmsを作成します。このオブジェクトのアプリケーションの多くは再帰的であるため、これは非常にきれいに機能します。

パフォーマンスは依然として私にとって優先度が高いので、呼び出しごとにパラメーターを保持する使い捨てオブジェクト配列を作成する必要はありませんでした。結局、大きなデータ構造では、何千もの要素があり、メッセージ処理にもあります。シナリオでは、1秒間に何千ものデータ構造を処理することになります。

スレッドセーフであるためには、パラメーター配列はAPIメソッドの呼び出しごとに一意に存在する必要があり、効率のために、コールバックの呼び出しごとに同じ配列を使用する必要があります。コールバックを呼び出し用のパラメーター配列にバインドするには、安価に作成できる2番目のオブジェクトが必要でした。ただし、一部のシナリオでは、呼び出し元は他の理由でパラメーター配列を既に持っています。これら2つの理由により、パラメーター配列はCallbackオブジェクトに属していません。また、呼び出しの選択(パラメーターを配列または個別のオブジェクトとして渡す)は、コールバックを使用してAPIの手に委ねられており、内部の仕組みに最も適した呼び出しを使用できます。

次に、WithParmsネストクラスはオプションであり、2つの目的を果たします。これには、コールバック呼び出しに必要なパラメーターオブジェクト配列が含まれ、パラメーター配列をロードする10個のオーバーロードされたinvoke()メソッド(1〜10個のパラメーター)が提供されます。コールバックターゲットを呼び出します。

以下は、コールバックを使用してディレクトリツリー内のファイルを処理する例です。これは、処理するファイルを数えて、事前に決定された最大サイズを超えないことを確認する最初の検証パスです。この場合、API呼び出しとインラインでコールバックを作成するだけです。ただし、ターゲットメソッドを静的な値として反映するため、反映が毎回行われるわけではありません。

static private final Method             COUNT =Callback.getMethod(Xxx.class,"callback_count",true,File.class,File.class);

...

IoUtil.processDirectory(root,new Callback(this,COUNT),selector);

...

private void callback_count(File dir, File fil) {
    if(fil!=null) {                                                                             // file is null for processing a directory
        fileTotal++;
        if(fil.length()>fileSizeLimit) {
            throw new Abort("Failed","File size exceeds maximum of "+TextUtil.formatNumber(fileSizeLimit)+" bytes: "+fil);
            }
        }
    progress("Counting",dir,fileTotal);
    }

IoUtil.processDirectory():

/**
 * Process a directory using callbacks.  To interrupt, the callback must throw an (unchecked) exception.
 * Subdirectories are processed only if the selector is null or selects the directories, and are done
 * after the files in any given directory.  When the callback is invoked for a directory, the file
 * argument is null;
 * <p>
 * The callback signature is:
 * <pre>    void callback(File dir, File ent);</pre>
 * <p>
 * @return          The number of files processed.
 */
static public int processDirectory(File dir, Callback cbk, FileSelector sel) {
    return _processDirectory(dir,new Callback.WithParms(cbk,2),sel);
    }

static private int _processDirectory(File dir, Callback.WithParms cbk, FileSelector sel) {
    int                                 cnt=0;

    if(!dir.isDirectory()) {
        if(sel==null || sel.accept(dir)) { cbk.invoke(dir.getParent(),dir); cnt++; }
        }
    else {
        cbk.invoke(dir,(Object[])null);

        File[] lst=(sel==null ? dir.listFiles() : dir.listFiles(sel));
        if(lst!=null) {
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(!ent.isDirectory()) {
                    cbk.invoke(dir,ent);
                    lst[xa]=null;
                    cnt++;
                    }
                }
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(ent!=null) { cnt+=_processDirectory(ent,cbk,sel); }
                }
            }
        }
    return cnt;
    }

この例は、このアプローチの美しさを示しています。アプリケーション固有のロジックがコールバックに抽象化され、ディレクトリツリーを再帰的に歩くという面倒な作業は、完全に再利用可能な静的ユーティリティメソッドにうまく組み込まれています。また、新しく使用するたびにインターフェイスを定義して実装するための代償を繰り返し支払う必要はありません。もちろん、インターフェースについての議論、何を実装するかについてはるかに明確であるということです(それは単に文書化されるのではなく、強制されます)-実際には、コールバック定義を正しくすることが問題であるとは知りませんでした。

インターフェイスを定義して実装することはそれほど悪いことではありません(私がそうであるようにアプレットを配布している場合を除いて、実際には余分なクラスを作成しないことが重要です)が、これが本当に優れているのは、単一のクラスに複数のコールバックがある場合です。それらを個別の内部クラスにプッシュする必要があるだけでなく、デプロイされたアプリケーションにオーバーヘッドが追加されるだけでなく、プログラムするのは実に面倒であり、すべてのボイラープレートコードは実際には単なる「ノイズ」です。


1

はい、いいえ、Javaのデリゲートパターンはこのように考えることができます。このビデオチュートリアルは、アクティビティ-フラグメント間のデータ交換に関するものであり、インターフェースを使用したソルタパターンのデリゲートの優れた本質を持っています。

Javaインターフェイス


1

delegateC#のような明示的なキーワードはありませんが、関数型インターフェイス(つまり、メソッドが1つだけの任意のインターフェイス)とラムダを使用することにより、Java 8で同様の機能を実現できます。

private interface SingleFunc {
    void printMe();
}

public static void main(String[] args) {
    SingleFunc sf = () -> {
        System.out.println("Hello, I am a simple single func.");
    };
    SingleFunc sfComplex = () -> {
        System.out.println("Hello, I am a COMPLEX single func.");
    };
    delegate(sf);
    delegate(sfComplex);
}

private static void delegate(SingleFunc f) {
    f.printMe();
}

タイプの新しいオブジェクトはすべてをSingleFunc実装する必要があるためprintMe()、別のメソッド(などdelegate(SingleFunc))に渡してメソッドを呼び出すのが安全printMe()です。


0

それほどきれいではありませんが、Java プロキシを使用してC#デリゲートのようなものを実装できます。


2
このリンクで質問に答えることができますが、回答の重要な部分をここに含め、参照用のリンクを提供することをお勧めします。リンクされたページが変更されると、リンクのみの回答が無効になる可能性があります。
StackFlowed 2014年

2
@StackFlowedリンクを取り除き、何を取得しますか?情報付きの投稿。維持するために投票してください。
Scimonster 2014年

1
@Scimonsterリンクが無効になった場合はどうなりますか?
StackFlowed 2014年

@StackFlowedそれでも答えを提供します-Javaプロキシを使用してください。
Scimonster 2014年

その後、コメントとして残すことができますか?
StackFlowed 2014年

0

いいえ、ただし内部的には同様の動作をします。

C#では、デリゲートは個別のエントリポイントを作成するために使用され、関数ポインターのように機能します。

Javaでは、(上向きに)関数ポインターとしての機能はありませんが、内部でJavaはこれらの目的を達成するために同じことを行う必要があります。

たとえば、Javaでスレッドを作成するには、クラスオブジェクト変数をメモリロケーションポインターとして使用できるため、Threadを拡張するか、Runnableを実装するクラスが必要です。



0

説明されているコードは、C#デリゲートの多くの利点を提供します。静的または動的な方法は、均一な方法で処理できます。ユーザーコードに追加のクラスを必要としないという意味で、リフレクションを介したメソッドの呼び出しの複雑さは軽減され、コードは再利用可能です。オブジェクト配列を作成せずに1つのパラメーターを持つメソッドを呼び出すことができる、invokeの代替の便利なバージョンを呼び出していることに注意してください。以下のJavaコード:

  class Class1 {
        public void show(String s) { System.out.println(s); }
    }

    class Class2 {
        public void display(String s) { System.out.println(s); }
    }

    // allows static method as well
    class Class3 {
        public static void staticDisplay(String s) { System.out.println(s); }
    }

    public class TestDelegate  {
        public static final Class[] OUTPUT_ARGS = { String.class };
        public final Delegator DO_SHOW = new Delegator(OUTPUT_ARGS,Void.TYPE);

        public void main(String[] args)  {
            Delegate[] items = new Delegate[3];

            items[0] = DO_SHOW .build(new Class1(),"show,);
            items[1] = DO_SHOW.build (new Class2(),"display");
            items[2] = DO_SHOW.build(Class3.class, "staticDisplay");

            for(int i = 0; i < items.length; i++) {
                items[i].invoke("Hello World");
            }
        }
    }

-11

Javaにはデリゲートがなく、誇りに思っています:)。私がここで読んだことから、私は本質的に、代理を偽造する2つの方法を見つけました。2.内部クラス

反射はすごい!内部クラスは、最も単純なユースケースであるソート関数をカバーしていません。詳細については説明しませんが、内部クラスの解決策は、基本的に整数の配列を昇順でソートするためのラッパークラスと、整数の配列を降順でソートするためのクラスを作成することです。


内部クラスはソートとどのように関係していますか?そして、質問と並べ替えは何をしていますか?
nawfal 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.