Java言語には、C#がデリゲートをサポートするのと同様に、デリゲート機能がありますか?
Java言語には、C#がデリゲートをサポートするのと同様に、デリゲート機能がありますか?
回答:
いいえ、違います。
リフレクションを使用して、次に呼び出すことができるMethodオブジェクトを取得することで同じ効果を達成できる場合があります。もう1つの方法は、単一の「invoke」または「execute」メソッドを使用してインターフェイスを作成し、インスタンス化してメソッドを呼び出すことです。興味がある(つまり、匿名の内部クラスを使用する)。
また、この記事は興味深い/役に立つかもしれません:JavaプログラマーがC#Delegates(@ archive.org)を見る
正確に何を意味するかに応じて、戦略パターンを使用して同様の効果(メソッドを渡す)を実現できます。
名前付きメソッドシグネチャを宣言するこのような行の代わりに:
// 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 */ });
前書き
Microsoft Visual J ++開発環境の最新バージョンは、デリゲートまたはバインドされたメソッド参照と呼ばれる言語構造をサポートしています。この構築、および新しいキーワード
delegate
とmulticast
、それをサポートするために導入には、ジャワの一部ではないTMの で指定されているプログラミング言語、Java言語仕様とによって改正仕様は内部クラスに含まJDKTM 1.1ソフトウェアのマニュアル。Javaプログラミング言語にこの構造が含まれることはほとんどありません。Sunは1996年に、実用的なプロトタイプを構築および破棄するまで、それを採用することを慎重に検討しました。私たちの結論は、バインドされたメソッド参照は不必要であり、言語に有害であるということでした。この決定は、Delphi Object Pascalでバインドされたメソッド参照に関する以前の経験を持つBorland Internationalと相談して行われました。
別の設計代替である内部クラスが同等または優れた機能を提供するため、バインドされたメソッド参照は不要であると考えています。特に、内部クラスはユーザーインターフェイスイベント処理の要件を完全にサポートし、少なくともWindows Foundation Classと同じくらい包括的なユーザーインターフェイスAPIの実装に使用されています。
バインドされたメソッド参照は、Javaプログラミング言語のシンプルさとAPIの広範囲に渡るオブジェクト指向の特性を損なうため、有害であると考えています。バインドされたメソッド参照は、言語の構文とスコープ規則に不規則性をもたらします。最後に、追加の異種の参照とメソッドのリンクを効率的に処理するためにVMが必要となるため、VMテクノロジーへの投資が薄れます。
あなたが読んで持っているこれを:
デリゲートは、イベントベースのシステムで役立つ構成です。基本的にデリゲートは、指定されたオブジェクトのメソッドディスパッチをエンコードするオブジェクトです。このドキュメントでは、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コンストラクトはこのより一般的な要件に対するソリューションを提供しないため、設計の基礎となるイディオムとして拒否する必要があります。
私はこの投稿が古いことを知っていますが、Java 8にはラムダと、1つのメソッドのみを持つ任意のインターフェースである関数型インターフェースの概念が追加されています。これらは共に、C#デリゲートと同様の機能を提供します。詳細についてはこちらをご覧ください。またはGoogle Java Lambdasをググってください。 http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
いいえ、ただしプロキシとリフレクションを使用して偽造できます。
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プラグインはここで助けます)、そして安全にそれを使用してさまざまなインスタンスに委任します。
リフレクションを使用して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;
}
この例は、このアプローチの美しさを示しています。アプリケーション固有のロジックがコールバックに抽象化され、ディレクトリツリーを再帰的に歩くという面倒な作業は、完全に再利用可能な静的ユーティリティメソッドにうまく組み込まれています。また、新しく使用するたびにインターフェイスを定義して実装するための代償を繰り返し支払う必要はありません。もちろん、インターフェースについての議論は、何を実装するかについてはるかに明確であるということです(それは単に文書化されるのではなく、強制されます)-実際には、コールバック定義を正しくすることが問題であるとは知りませんでした。
インターフェイスを定義して実装することはそれほど悪いことではありません(私がそうであるようにアプレットを配布している場合を除いて、実際には余分なクラスを作成しないことが重要です)が、これが本当に優れているのは、単一のクラスに複数のコールバックがある場合です。それらを個別の内部クラスにプッシュする必要があるだけでなく、デプロイされたアプリケーションにオーバーヘッドが追加されるだけでなく、プログラムするのは実に面倒であり、すべてのボイラープレートコードは実際には単なる「ノイズ」です。
はい、いいえ、Javaのデリゲートパターンはこのように考えることができます。このビデオチュートリアルは、アクティビティ-フラグメント間のデータ交換に関するものであり、インターフェースを使用したソルタパターンのデリゲートの優れた本質を持っています。
delegate
C#のような明示的なキーワードはありませんが、関数型インターフェイス(つまり、メソッドが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()
です。
それほどきれいではありませんが、Java プロキシを使用してC#デリゲートのようなものを実装できます。
いいえ、ただし内部的には同様の動作をします。
C#では、デリゲートは個別のエントリポイントを作成するために使用され、関数ポインターのように機能します。
Javaでは、(上向きに)関数ポインターとしての機能はありませんが、内部でJavaはこれらの目的を達成するために同じことを行う必要があります。
たとえば、Javaでスレッドを作成するには、クラスオブジェクト変数をメモリロケーションポインターとして使用できるため、Threadを拡張するか、Runnableを実装するクラスが必要です。
いいえ、Javaにはそのすばらしい機能はありません。ただし、オブザーバーパターンを使用して手動で作成することもできます。次に例を示します 。JavaでC#デリゲートを記述します。
説明されているコードは、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");
}
}
}