Javaの関数ポインター


148

これは一般的で些細なことかもしれませんが、具体的な答えを見つけるのに苦労しているようです。C#にはデリゲートの概念があり、これはC ++からの関数ポインターの概念に強く関連しています。Javaに同様の機能はありますか?ポインタがいくらか欠けていることを考えると、これについての最良の方法は何ですか?明確にするために、ここではファーストクラスについて話します。


1
オブジェクトの性質を保持しながら、リスナーやその他のOOP構文が同じタスクを実行するのに、なぜこれが必要なのかについて、私は興味があります。つまり、単純なオブジェクトで実現できるという概念によって提供される機能の必要性を理解しているということです。:-)
ニュートピアン2009

2
Java 8にはラムダ式があります:docs.oracle.com/javase/tutorial/java/javaOO/…これを確認してください。完全な関数ポインタではありませんが、まだもっと役立つかもしれません。
FrustratedWithFormsDesigner

6
Java 8メソッド参照はまさにあなたが求めているものです。
Steven

8
Java 8 メソッド参照はまさにあなたが求めているものです。 this::myMethod意味的には、ラムダを作成するのと同じparamA, paramB -> this.myMethod(paramA, paramB)です。
Steven

回答:


126

関数ポインタのような機能の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);

2
戦略デザインパターン。関数に引数として提供されていない非グローバルデータを関数で使用する場合、CまたはC ++関数ポインターほど醜くない。
Raedwald、2011年

75
@Raedwald C ++にはファンクタがあり、C ++ 0xにはファンクタの上に構築されたクロージャとラムダがあります。std::bindパラメータを関数にバインドし、呼び出し可能なオブジェクトを返すも備えています。これらの理由でCを防御することはできませんが、C ++ Javaよりも優れています。
zneak

21
@ zneak、@ Raedwald:Cでこれを行うには、ユーザーデータへのポインターを渡します(例:構造体)。(そして、コールバック間接化の新しいレベルごとにそれをカプセル化することができます)それは実際にはかなりクリーンです。
ブライス

25
ええ、私は実際にはいつでもCの関数ポインターを無愛想なラッパークラスに乗せます
BT

3
これにはJava 8の方が構文が優れています。
するThorbjörnRavnアンデルセン

65

インターフェイスを使用して関数ポインターを置き換えることができます。コレクションを実行して、各要素で何かをしたいとしましょう。

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

42

リフレクションを使用してそれを行うことができます。

オブジェクトとメソッド名を(文字列として)パラメータとして渡し、メソッドを呼び出します。例えば:

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");

もちろん、すべての例外を確認し、必要なキャストを追加してください。


7
リフレクションは、「遅い怪しげなJavaコードをどのように書くのですか」を除いて、正しい答えではありません:D
Jacob

5
「醜い」と遅いかもしれませんが、リフレクションによって、受け入れられた回答のインターフェースベースのソリューションでは実行できないこと(私が間違っていない限り)、つまり同じコード内で同じオブジェクトのさまざまなメソッドを呼び出すことができると思います部分。
エウセビウス

@zoquete ..私はこの投稿を行っていて、疑いを持っていました..したがって、あなたのアプローチでは、変数「theDescription」にアクセスすると、変数がアクセスされるたびに関数が呼び出されますか?
karthik27 2014

20

いいえ、関数はJavaのファーストクラスオブジェクトではありません。ハンドラクラスを実装することで同じことができます-これは、Swingなどでコールバックが実装される方法です。

ただし、Javaの将来のバージョンでは、クロージャー(あなたが話していることの正式名称)についての提案があります。Javaworldには興味深い記事があります。


15

これは、名詞の王国におけるスティーブ・イェッジの処刑を思い起こさせます。基本的に、Javaはすべてのアクションに対してオブジェクトを必要とするため、関数ポインタのような「動詞のみ」のエンティティはありません。


8

同様の機能を実現するには、匿名の内部クラスを使用できます。

インターフェースを定義する場合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.
    }
}

7

Java8はラムダとメソッド参照を導入しました。したがって、関数が関数インターフェイスと一致する場合(独自の関数を作成できます)、この場合はメソッド参照を使用できます。

Javaは、共通の機能インターフェースのセットを提供します。一方、次のことができます。

public class Test {
   public void test1(Integer i) {}
   public void test2(Integer i) {}
   public void consumer(Consumer<Integer> a) {
     a.accept(10);
   }
   public void provideConsumer() {
     consumer(this::test1);   // method reference
     consumer(x -> test2(x)); // lambda
   }
}

のコンセプトはありaliasますか:例 public List<T> collect(T t) = Collections::collect
javadba

私が知らない限り@javadba。
アレックス

5

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

3

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

使い方

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

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

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

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



-1

ここのほとんどの人に比べて、私はJavaを初めて使用しますが、同様の提案をまだ見ていなかったので、別の提案があります。それが良い習慣であるかどうかはわかりませんが、以前に提案されていても、私はそれを理解できませんでした。自己説明的だと思うので、私はそれが好きです。

 /*Just to merge functions in a common name*/
 public class CustomFunction{ 
 public CustomFunction(){}
 }

 /*Actual functions*/
 public class Function1 extends CustomFunction{
 public Function1(){}
 public void execute(){...something here...}
 }

 public class Function2 extends CustomFunction{
 public Function2(){}
 public void execute(){...something here...}
 }

 .....
 /*in Main class*/
 CustomFunction functionpointer = null;

次に、アプリケーションに応じて、割り当てます

 functionpointer = new Function1();
 functionpointer = new Function2();

そして電話する

 functionpointer.execute();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.