Javaの関数ポインターの最も近い代替物は何ですか?


299

約10行のコードを使用するメソッドがあります。コードの1行を変更する小さな計算を除いて、まったく同じことを行うメソッドをさらに作成したいと思います。これは関数ポインターを渡してその1行を置き換えるのに最適なアプリケーションですが、Javaには関数ポインターがありません。私の最善の選択肢は何ですか?


5
Java 8にはラムダ式が含まれます。ラムダ式の詳細については、こちらをご覧ください
マリウス

4
@マリウスラムダ式は関数ポインタとして数えるとは思いません。一方、オペレータ、...::
帽子の男

コメントが遅くなってすみません;)-通常、そのための関数ポインタは必要ありません。テンプレートメソッドを使用するだけです。(en.wikipedia.org/wiki/Template_method_pattern
isnot2bad

2
@ isnot2bad-その記事を見るとやりすぎのようです-ここでの回答よりも複雑です。特に、テンプレートメソッドでは、代替計算ごとにサブクラスを作成する必要があります。OPがサブクラスを必要とすることを何も述べていないようです。彼は単にいくつかのメソッドを作成し、ほとんどの実装を共有したいと考えています。受け入れられた答えが示すように、これは、ラムダを備えたJava 8の前でさえ、(各メソッド内で)「インプレース」で簡単に実行されます。
ToolmakerSteve

@ToolmakerSteve承認されたソリューションでは、計算ごとのクラスも必要です(たとえそれが匿名の内部クラスであっても)。また、テンプレートメソッドパターンは匿名の内部クラスを使用して実現することもできるため、(Java 8より前の)オーバーヘッドに関する一般的な解決策とそれほど変わりません。したがって、それは使用パターンと詳細な要件の問題であり、私たちにはわかりません。受け入れられた答えに感謝し、考えられる別の可能性を追加したかっただけです。
isnot2bad 2015

回答:


269

匿名の内部クラス

Stringたとえば、を返すparamを指定して関数を渡したいとしますint
既存のインターフェイスを再利用できない場合は、最初に関数を唯一のメンバーとして持つインターフェイスを定義する必要があります。

interface StringFunction {
    int func(String param);
}

ポインタを受け取るメソッドは、次のようにStringFunctionインスタンスを受け入れるだけです。

public void takingMethod(StringFunction sf) {
   int i = sf.func("my string");
   // do whatever ...
}

そして、そのように呼ばれます:

ref.takingMethod(new StringFunction() {
    public int func(String param) {
        // body
    }
});

編集: Java 8では、ラムダ式で呼び出すことができます:

ref.takingMethod(param -> bodyExpression);

13
ちなみに、これは「コマンドパターン」の例です。en.wikipedia.org/wiki/Command_Pattern
Ogre Psalm33 '09 / 10/09

3
@Ogre Psalm33この手法は、使い方によっては戦略パターンにもなる可能性があります。戦略パターンとコマンドパターンの違い
Rory O'Kane、2011

2
Java 5、6、および7のクロージャー実装は 次のとおりです。mseifed.blogspot.se/ 2012/09 / …要求できるものがすべて含まれています...とてもすばらしいと思います!
mmm

@SecretService:そのリンクは死んでいます。
Lawrence Dol 2014

@LawrenceDolええ、そうです。ここに私が使用しているクラスのペーストビンがあります。pastebin.com/b1j3q2Lp
mmm


28

定義済みの数の異なる計算がその1行で実行できる場合、列挙型を使用すると、戦略パターンを実装するための迅速かつ明確な方法になります。

public enum Operation {
    PLUS {
        public double calc(double a, double b) {
            return a + b;
        }
    },
    TIMES {
        public double calc(double a, double b) {
            return a * b;
        }
    }
     ...

     public abstract double calc(double a, double b);
}

明らかに、戦略メソッド宣言と各実装の正確に1つのインスタンスはすべて、単一のクラス/ファイルで定義されます。


24

渡したい関数を提供するインターフェースを作成する必要があります。例えば:

/**
 * A simple interface to wrap up a function of one argument.
 * 
 * @author rcreswick
 *
 */
public interface Function1<S, T> {

   /**
    * Evaluates this function on it's arguments.
    * 
    * @param a The first argument.
    * @return The result.
    */
   public S eval(T a);

}

次に、関数を渡す必要がある場合は、そのインターフェースを実装できます。

List<Integer> result = CollectionUtilities.map(list,
        new Function1<Integer, Integer>() {
           @Override
           public Integer eval(Integer a) {
              return a * a;
           }
        });

最後に、map関数は渡されたFunction1を次のように使用します。

   public static <K,R,S,T> Map<K, R> zipWith(Function2<R,S,T> fn, 
         Map<K, S> m1, Map<K, T> m2, Map<K, R> results){
      Set<K> keySet = new HashSet<K>();
      keySet.addAll(m1.keySet());
      keySet.addAll(m2.keySet());

      results.clear();

      for (K key : keySet) {
         results.put(key, fn.eval(m1.get(key), m2.get(key)));
      }
      return results;
   }

多くの場合、パラメーターを渡す必要がない場合は、独自のインターフェースの代わりにRunnableを使用できます。または、他のさまざまな手法を使用して、パラメーターカウントの「固定」を少なくすることもできますが、これは通常、タイプセーフティとのトレードオフです。(または、関数オブジェクトのコンストラクターをオーバーライドして、その方法でparamsを渡すことができます。多くのアプローチがあり、いくつかは特定の状況でよりよく機能します。)


4
この「答え」は、ソリューションセットよりも問題セットに関係しています。☹– tchrist 2010
19:46

18

::演算子を使用したメソッド参照

メソッドが関数インターフェイスを受け入れる場合、メソッド引数でメソッド参照を使用できます。機能インターフェースは、1つの抽象メソッドのみを含むインターフェースです。(機能インターフェースには、1つ以上のデフォルトのメソッドまたは静的メソッドが含まれる場合があります。)

IntBinaryOperator機能的なインターフェースです。その抽象メソッドはapplyAsInt、2つintのをパラメーターとして受け入れ、を返しますintMath.maxまた、2つintのを受け入れ、を返しますint。この例でA.method(Math::max);は、parameter.applyAsIntmakeは2つの入力値をに送信し、そのMath.max結果を返しMath.maxます。

import java.util.function.IntBinaryOperator;

class A {
    static void method(IntBinaryOperator parameter) {
        int i = parameter.applyAsInt(7315, 89163);
        System.out.println(i);
    }
}
import java.lang.Math;

class B {
    public static void main(String[] args) {
        A.method(Math::max);
    }
}

一般的に、次のものを使用できます。

method1(Class1::method2);

の代わりに:

method1((arg1, arg2) -> Class1.method2(arg1, arg2));

これは以下の略です:

method1(new Interface1() {
    int method1(int arg1, int arg2) {
        return Class1.method2(arg1, agr2);
    }
});

詳細については、Java 8の: :(ダブルコロン)演算子およびJava言語仕様§15.13を参照してください


15

これを行うこともできます(一部のRAREの場合、これは理にかなっています)。問題(そしてそれは大きな問題です)は、クラス/インターフェースを使用することのすべての型保証が失われ、メソッドが存在しない場合に対処しなければならないことです。

これには、アクセス制限を無視してプライベートメソッドを呼び出すことができる「利点」があります(例には示されていませんが、コンパイラーが通常は呼び出せないメソッドを呼び出すことができます)。

繰り返しになりますが、これが理にかなっているのはまれなケースですが、そのような場合に備えておくと便利です。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class Main
{
    public static void main(final String[] argv)
        throws NoSuchMethodException,
               IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        final String methodName;
        final Method method;
        final Main   main;

        main = new Main();

        if(argv.length == 0)
        {
            methodName = "foo";
        }
        else
        {
            methodName = "bar";
        }

        method = Main.class.getDeclaredMethod(methodName, int.class);

        main.car(method, 42);
    }

    private void foo(final int x)
    {
        System.out.println("foo: " + x);
    }

    private void bar(final int x)
    {
        System.out.println("bar: " + x);
    }

    private void car(final Method method,
                     final int    val)
        throws IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        method.invoke(this, val);
    }
}

1
メソッドの構文は匿名の内部クラスの構文よりもはるかに単純であるため、これをメニュー処理/ GUIに使用することがあります。それはすばらしいですが、一部の人々が掘り下げたくない反射の複雑さを追加しているので、それを正しく理解し、起こり得るすべてのエラー条件に対して明確なテキストエラーがあることを確認してください。
ビルK

ジェネリックを使用してタイプセーフに行うことができ、リフレクションは必要ありません。
Luigi Plinge

1
ジェネリックを使用してリフレクションを使用しないと、文字列に含まれている名前でメソッドを呼び出すことができますか?
TofuBeer

1
@LuigiPlinge-意味のコードスニペットを提供できますか?
ToolmakerSteve

13

異なる行が1つしかない場合は、フラグなどのパラメーターと、いずれかの行を呼び出すif(flag)ステートメントを追加できます。


1
3つ以上の計算バリアントがある場合、javaslookの答えはこれを行うためのよりクリーンな方法のようです。または、コードをメソッドに埋め込む場合は、メソッドが処理するさまざまなケースの列挙型とスイッチ。
ToolmakerSteve、2015

1
真@ToolmakerSteve、今日は、Java 8のラムダを使うだろうが
ピーターLawrey


11

演算子を使用した新しいJava 8 機能インターフェースメソッド参照::

Java 8は、 " @ Functional Interface "ポインターを使用してメソッド参照(MyClass :: new)を維持できます。同じメソッド名は必要なく、同じメソッドシグネチャのみが必要です。

例:

@FunctionalInterface
interface CallbackHandler{
    public void onClick();
}

public class MyClass{
    public void doClick1(){System.out.println("doClick1");;}
    public void doClick2(){System.out.println("doClick2");}
    public CallbackHandler mClickListener = this::doClick;

    public static void main(String[] args) {
        MyClass myObjectInstance = new MyClass();
        CallbackHandler pointer = myObjectInstance::doClick1;
        Runnable pointer2 = myObjectInstance::doClick2;
        pointer.onClick();
        pointer2.run();
    }
}

それで、ここには何がありますか?

  1. 機能インターフェース-これは、@ FunctionalInterfaceで注釈が付けられているかどうかにかかわらず、メソッド宣言を1つだけ含むインターフェースです。
  2. メソッド参照-これは特別な構文で、次のようになります、objectInstance :: methodName
  3. 使用例-代入演算子とインターフェイスメソッドの呼び出しのみ。

あなたはリスナーのためだけに、そしてそれのためだけに機能的なインターフェースを使用すべきです!

他のすべてのそのような関数ポインターは、コードの可読性と理解能力にとって本当に悪いからです。ただし、直接メソッド参照は、たとえばforeachなどの場合に便利です。

いくつかの事前定義された機能インターフェースがあります。

Runnable              -> void run( );
Supplier<T>           -> T get( );
Consumer<T>           -> void accept(T);
Predicate<T>          -> boolean test(T);
UnaryOperator<T>      -> T apply(T);
BinaryOperator<T,U,R> -> R apply(T, U);
Function<T,R>         -> R apply(T);
BiFunction<T,U,R>     -> R apply(T, U);
//... and some more of it ...
Callable<V>           -> V call() throws Exception;
Readable              -> int read(CharBuffer) throws IOException;
AutoCloseable         -> void close() throws Exception;
Iterable<T>           -> Iterator<T> iterator();
Comparable<T>         -> int compareTo(T);
Comparator<T>         -> int compare(T,T);

以前のJavaバージョンでは、Adrian Petrescuが前述したように、同様の機能と構文を備えたGuava Librariesを試す必要があります。

追加の調査については、Java 8チートシートをご覧ください。

そして、Java言語仕様§15.13リンクのための帽子を持つ男に感謝します。


7
他のすべては...コードの可読性に本当に悪いので」は完全に根拠のない主張であり、しかも間違っています。
ローレンスドル2014

9

@sblundyの答えは素晴らしいですが、匿名の内部クラスには2つの小さな欠陥があります。主な理由は、再利用できない傾向があり、2番目はかさばる構文です。

良い点は、彼のパターンがメインクラス(計算を実行するクラス)を変更せずに完全なクラスに拡張されることです。

新しいクラスをインスタンス化するときに、方程式の定数として機能できるパラメーターをそのクラスに渡すことができます。つまり、内部クラスの1つが次のようになっている場合です。

f(x,y)=x*y

しかし時々あなたはそれが必要です:

f(x,y)=x*y*2

そしておそらく3分の1は:

f(x,y)=x*y/2

2つの匿名内部クラスを作成したり、「パススルー」パラメーターを追加する代わりに、次のようにインスタンス化する単一のACTUALクラスを作成できます。

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f=new InnerFunc(2.0);// for the second
calculateUsing(f);
f=new InnerFunc(0.5);// for the third
calculateUsing(f);

クラスに定数を格納し、インターフェイスで指定されたメソッドで使用するだけです。

実際、関数が保存/再利用されないことがわかっている場合は、次のようにすることができます。

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f.setConstant(2.0);
calculateUsing(f);
f.setConstant(0.5);
calculateUsing(f);

しかし、不変クラスの方が安全です。このようなクラスを変更可能にする理由を考え出すことはできません。

匿名の内部クラスを聞くたびにうんざりするので、私は本当にこれだけを投稿します。プログラマーが最初に行ったのは、実際のクラスを使用する必要があるときに匿名にしたため、「必須」である冗長なコードをたくさん見ました彼の決定を再考した。


え?OPはさまざまな計算(アルゴリズム、ロジック)について話します。異なる(データ)を表示しています。違いを値に組み込むことができる特定のケースを示していますが、これは、提起されている問題の正当化できない単純化です。
ToolmakerSteve

6

非常に人気が高まっているGoogle グアバライブラリには、APIの多くの部分に組み込まれている汎用のFunctionおよびPredicateオブジェクトがあります。


この回答は、コードの詳細を提供する場合に、より役立ちます。受け入れられた回答に示されているコードを受け取り、関数を使用してそれがどのように見えるかを示します。
ToolmakerSteve

4

私には戦略パターンのように聞こえます。fluffycat.comのJavaパターンを調べてください。


4

Javaでプログラミングするときに私が本当に見逃しているものの1つは、関数のコールバックです。これらが常に表示される必要がある状況の1つは、階層ごとに再帰的に処理する階層で、各アイテムに対して特定のアクションを実行したい場合です。ディレクトリツリーを歩く、またはデータ構造を処理するようなものです。私の中のミニマリストは、特定のケースごとにインターフェースを定義してから実装を定義する必要があることを嫌っています。

ある日、なぜだろうと思いました。メソッドポインター-メソッドオブジェクトがあります。JITコンパイラーを最適化することで、リフレクティブな呼び出しがパフォーマンスに大きな影響を与えることはもうありません。そして、たとえば、ある場所から別の場所にファイルをコピーする以外に、反映されたメソッド呼び出しのコストは取るに足らないものになります。

それについてもっと考えたとき、OOPパラダイムでのコールバックにはオブジェクトとメソッドを一緒にバインドする必要があることに気付きました-Callbackオブジェクトを入力してください。

Javaのコールバックのリフレクションベースのソリューションを確認してください。使用は無料です。


4

OK、このスレッドはすでに古くなっているので、おそらく私の答えは質問には役に立たないでしょう。しかし、このスレッドは私の解決策を見つけるのに役立ちましたので、とにかくここに公開します。

既知の入力と既知の出力(両方ともdouble)を持つ可変静的メソッドを使用する必要がありました。したがって、メソッドのパッケージと名前を知っているので、次のように作業できます。

java.lang.reflect.Method Function = Class.forName(String classPath).getMethod(String method, Class[] params);

1つのdoubleをパラメーターとして受け入れる関数の場合。

したがって、私の具体的な状況では、

java.lang.reflect.Method Function = Class.forName("be.qan.NN.ActivationFunctions").getMethod("sigmoid", double.class);

後でより複雑な状況でそれを呼び出しました

return (java.lang.Double)this.Function.invoke(null, args);

java.lang.Object[] args = new java.lang.Object[] {activity};
someOtherFunction() + 234 + (java.lang.Double)Function.invoke(null, args);

ここで、アクティビティは任意のdouble値です。SoftwareMonkeyが行ったように、これをもう少し抽象化して一般化することを考えていますが、現在はそれで十分満足しています。3行のコード。クラスやインターフェースは必要ありません。それほど悪くはありません。



4

関数の配列のインターフェースなしで同じことを行うには:

class NameFuncPair
{
    public String name;                // name each func
    void   f(String x) {}              // stub gets overridden
    public NameFuncPair(String myName) { this.name = myName; }
}

public class ArrayOfFunctions
{
    public static void main(String[] args)
    {
        final A a = new A();
        final B b = new B();

        NameFuncPair[] fArray = new NameFuncPair[]
        {
            new NameFuncPair("A") { @Override void f(String x) { a.g(x); } },
            new NameFuncPair("B") { @Override void f(String x) { b.h(x); } },
        };

        // Go through the whole func list and run the func named "B"
        for (NameFuncPair fInstance : fArray)
        {
            if (fInstance.name.equals("B"))
            {
                fInstance.f(fInstance.name + "(some args)");
            }
        }
    }
}

class A { void g(String args) { System.out.println(args); } }
class B { void h(String args) { System.out.println(args); } }

1
どうして?これは、以前に提案されたソリューションよりも複雑です。これは、代替ごとに1つの匿名関数定義が必要なだけです。別の方法として、クラスと無名関数の定義を作成します。さらに悪いことに、これはコードの2つの異なる場所で行われます。このアプローチを使用する正当な理由を提供したい場合があります。
ToolmakerSteve


3

うわー、私がすでにJavaに対して行ったことを考えると、それほど難しくないDelegateクラスを作成し、それを使用してTが戻り値の型であるパラメーターを渡すのではないでしょうか。申し訳ありませんが、C ++ / C#プログラマーとして一般的にJavaを習得しているだけなので、非常に便利な関数ポインターが必要です。メソッド情報を扱うクラスに精通している場合は、それを行うことができます。javaライブラリでは、java.lang.reflect.methodになります。

常にインターフェースを使用する場合は、常にそれを実装する必要があります。イベント処理では、ハンドラーのリストへの登録/登録解除を行うのに最適な方法はありませんが、値型ではなく関数を渡す必要があるデリゲートの場合は、デリゲートクラスを作成して、インターフェースをアウトクラスで処理します。


コードの詳細を表示しない限り、有用な回答ではありません。デリゲートクラスの作成はどのように役立ちますか?代替案ごとにどのコードが必要ですか?
ToolmakerSteve

2

Java 8の答えのどれも完全でまとまりのある例を提供していないので、ここにそれが来る。

次のように、「関数ポインタ」を受け入れるメソッドを宣言します。

void doCalculation(Function<Integer, String> calculation, int parameter) {
    final String result = calculation.apply(parameter);
}

関数にラムダ式を指定して呼び出します。

doCalculation((i) -> i.toString(), 2);


1

Java8以降、ラムダを使用できます。ラムダには、公式のSE 8 APIにライブラリがあります。

使用法: 抽象メソッドを1つだけ持つインターフェースを使用する必要があります。そのインスタンスを作成します(すでに提供されている1つのJava SE 8を使用することもできます)。

Function<InputType, OutputType> functionname = (inputvariablename) {
... 
return outputinstance;
}

詳細については、ドキュメントをチェックアウトしてくださいhttps : //docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html


1

Java 8より前は、関数ポインターのような機能の最も近い代替物は匿名クラスでした。例えば:

Collections.sort(list, new Comparator<CustomClass>(){
    public int compare(CustomClass a, CustomClass b)
    {
        // Logic to compare objects of class CustomClass which returns int as per contract.
    }
});

しかし、現在Java 8 では、ラムダ式と呼ばれる非常に優れた代替手段があり、次のように使用できます。

list.sort((a, b) ->  { a.isBiggerThan(b) } );

ここで、isBiggerThanはのメソッドですCustomClass。ここでメソッド参照を使用することもできます。

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