web-dev-qa-db-ja.com

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

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

299
Bill the Lizard

匿名の内部クラス

Stringを返す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
    }
});

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

ref.takingMethod(param -> bodyExpression);
269
sblundy

「関数ポインター」ごとに、計算を実装する小さな functor class を作成します。すべてのクラスが実装するインターフェイスを定義し、それらのオブジェクトのインスタンスをより大きな関数に渡します。これは、「 コマンドパターン 」と「 戦略パターン 」の組み合わせです。

@sblundyの例は良いです。

32
Blair Conrad

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

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つのインスタンスはすべて、単一のクラス/ファイルで定義されます。

28
javashlook

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

/**
 * 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を使用することがよくあります。また、他のさまざまな手法を使用してparamカウントを「固定」しないようにすることもできますが、通常はタイプセーフとのトレードオフです。 (または、関数オブジェクトのコンストラクタをオーバーライドして、そのようにparamsを渡すことができます。多くのアプローチがあり、特定の状況でより適切に機能するものもあります。)

24
rcreswick

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

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

IntBinaryOperator は機能的なインターフェースです。その抽象メソッド applyAsInt は、パラメーターとして2つのintsを受け入れ、intを返します。 Math.max は、2つのintsも受け入れ、intを返します。この例では、A.method(Math::max);parameter.applyAsIntに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

17

これを行うこともできます(一部の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);
    }
}
15
TofuBeer

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

13
Peter Lawrey

また、クロージャが関係するJava 7の作業について聞いてみたいかもしれません。

Javaのクロージャの現在の状態は?

http://gafter.blogspot.com/2006/08/closures-for-Java.html
http://tech.puredanger.com/Java7/#closures

12
Dave L.

新しいJava 8Functional InterfacesおよびMethod Referencesusing ::演算子。

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 Cheatsheet をご覧ください。

Java Language Specification§15.1 リンクについてThe The Guy with The Hatに感謝します。

11
user3002379

@sblundyの答えは素晴らしいですが、匿名の内部クラスには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);

しかし、不変のクラスの方が安全です。このようなクラスを可変にする正当な理由は思いつきません。

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

9
Bill K

Google Guavaライブラリ は非常に人気が出ており、APIの多くの部分で機能する汎用の Function および Predicate オブジェクトがあります。

6
Adrian Petrescu

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
yogibimbi

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

ある日、私はなぜそうなのだろうと思いましたか?メソッドポインター-Methodオブジェクトがあります。 JITコンパイラーを最適化することで、リフレクション呼び出しは実際に大きなパフォーマンスの低下をもたらしません。そして、たとえば、ある場所から別の場所にファイルをコピーすることに加えて、反映されたメソッド呼び出しのコストは重要ではありません。

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

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

4
Lawrence Dol

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

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); } }
4
vwvan

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

4
Dennis S

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

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

3
Robert

Lambdajをチェックしてください

http://code.google.com/p/lambdaj/

特にその新しい閉鎖機能

http://code.google.com/p/lambdaj/wiki/Closures

意味のないインターフェイスを作成したり、見苦しい内部クラスを使用したりせずに、クロージャーまたは関数ポインターを定義する非常に読みやすい方法が見つかります。

3
Mario Fusco

Java 8の回答はどれも完全でまとまりのある例を示していないので、ここで説明します。

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

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

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

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

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には lambda expression として知られる非常にきちんとした代替手段があり、これは次のように使用できます:

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

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

list.sort(MyClass::isBiggerThan);
1
i_am_zero

誰かがその動作を定義するパラメータのセットを受け取り、Schemeのように実行する別のパラメータのセットを受け取る関数を渡すのに苦労している場合:

(define (function scalar1 scalar2)
  (lambda (x) (* x scalar1 scalar2)))

Javaでのパラメーター定義の動作を伴うパス関数 を参照してください

1
Scott Emmons

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

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

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

詳細については、ドキュメントを参照してください。 https://docs.Oracle.com/javase/tutorial/Java/javaOO/lambdaexpressions.html

1
Alex