これは一般的で些細なことかもしれませんが、具体的な答えを見つけるのに苦労しているようです. C# にはデリゲートの概念があり、これは C++ の関数ポインターの概念に強く関連しています。Javaに同様の機能はありますか? ポインターがやや欠けていることを考えると、これについての最善の方法は何ですか? 明確にするために、ここではファーストクラスについて話しています。
11 に答える
関数ポインタのような機能の Java イディオムは、インターフェイスを実装する匿名クラスです。
Collections.sort(list, new Comparator<MyClass>(){
public int compare(MyClass a, MyClass b)
{
// compare objects
}
});
更新: Java 8 より前の Java バージョンでは上記が必要です。
list.sort((a, b) -> a.isGreaterThan(b));
およびメソッド参照:
list.sort(MyClass::isGreaterThan);
関数ポインターをインターフェイスに置き換えることができます。コレクションを実行し、各要素で何かをしたいとしましょう。
public interface IFunction {
public void execute(Object o);
}
これは、たとえば CollectionUtils2.doFunc(Collection c, IFunction f) に渡すことができるインターフェイスです。
public static void doFunc(Collection c, IFunction f) {
for (Object o : c) {
f.execute(o);
}
}
例として、数値のコレクションがあり、すべての要素に 1 を追加したいとします。
CollectionUtils2.doFunc(List numbers, new IFunction() {
public void execute(Object o) {
Integer anInt = (Integer) o;
anInt++;
}
});
リフレクションを使用してそれを行うことができます。
パラメータとしてオブジェクトとメソッド名 (文字列として) を渡し、メソッドを呼び出します。例えば:
Object methodCaller(Object theObject, String methodName) {
return theObject.getClass().getMethod(methodName).invoke(theObject);
// Catch the exceptions
}
そして、次のように使用します。
String theDescription = methodCaller(object1, "toString");
Class theClass = methodCaller(object2, "getClass");
もちろん、すべての例外を確認し、必要なキャストを追加してください。
いいえ、関数はJavaのファーストクラスオブジェクトではありません。ハンドラークラスを実装することで同じことを行うことができます-これは、Swingなどでコールバックが実装される方法です。
ただし、Javaの将来のバージョンでは、クロージャ(あなたが話していることの正式な名前)の提案があります-Javaworldには興味深い記事があります。
これは、Steve Yegge の「名詞の王国での処刑」を思い起こさせます。基本的に、Java はすべてのアクションにオブジェクトが必要であり、したがって関数ポインタのような「動詞のみ」のエンティティはないと述べています。
同様の機能を実現するには、匿名の内部クラスを使用できます。
インターフェイスを定義する場合Foo
:
interface Foo {
Object myFunc(Object arg);
}
bar
「関数ポインタ」を引数として受け取るメソッドを作成します。
public void bar(Foo foo) {
// .....
Object object = foo.myFunc(argValue);
// .....
}
最後に、次のようにメソッドを呼び出します。
bar(new Foo() {
public Object myFunc(Object arg) {
// Function code.
}
}
Javaにはそのようなものはありません。関数を何らかのオブジェクトにラップし、そのオブジェクトのメソッドへの参照を渡すために、そのオブジェクトへの参照を渡す必要があります。
構文的には、これは、インプレースで定義された匿名クラスまたはクラスのメンバー変数として定義された匿名クラスを使用することで、ある程度緩和できます。
例:
class MyComponent extends JPanel {
private JButton button;
public MyComponent() {
button = new JButton("click me");
button.addActionListener(buttonAction);
add(button);
}
private ActionListener buttonAction = new ActionListener() {
public void actionPerformed(ActionEvent e) {
// handle the event...
// note how the handler instance can access
// members of the surrounding class
button.setText("you clicked me");
}
}
}
リフレクションを使用して、Javaでコールバック/デリゲートのサポートを実装しました。詳細と作業ソースは私のウェブサイトで入手できます。
使い方
WithParmsという名前のネストされたクラスを持つCallbackという名前のプリンシパルクラスがあります。コールバックを必要とするAPIは、パラメーターとしてCallbackオブジェクトを受け取り、必要に応じて、メソッド変数としてCallback.WithParmsを作成します。このオブジェクトのアプリケーションの多くは再帰的であるため、これは非常にきれいに機能します。
パフォーマンスは依然として私にとって優先度が高いので、すべての呼び出しのパラメーターを保持する使い捨てオブジェクト配列を作成する必要はありませんでした-結局のところ、大規模なデータ構造では数千の要素が存在する可能性があり、メッセージ処理ではシナリオでは、1秒間に数千のデータ構造を処理することになります。
スレッドセーフであるためには、APIメソッドの呼び出しごとにパラメーター配列が一意に存在する必要があり、効率を上げるために、コールバックの呼び出しごとに同じ配列を使用する必要があります。コールバックを呼び出し用のパラメーター配列にバインドするために、安価に作成できる2番目のオブジェクトが必要でした。ただし、一部のシナリオでは、他の理由で、呼び出し元がすでにパラメーター配列を持っている場合があります。これらの2つの理由により、パラメーター配列はCallbackオブジェクトに属していませんでした。また、呼び出しの選択(パラメーターを配列または個々のオブジェクトとして渡す)は、コールバックを使用するAPIの手に委ねられ、内部の動作に最も適した呼び出しを使用できるようにします。
したがって、WithParmsネストされたクラスはオプションであり、2つの目的を果たします。これには、コールバック呼び出しに必要なパラメーターオブジェクト配列が含まれ、パラメーター配列をロードしてからパラメーター配列をロードする10個のオーバーロードされたinvoke()メソッド(1〜10個のパラメーター)を提供します。コールバックターゲットを呼び出します。
lambdaj ライブラリでクロージャがどのように実装されているかを確認してください。実際には、C# デリゲートと非常によく似た動作をします。