Java の関数ポインタに最も近い代替物は何ですか?
-
02-07-2019 - |
質問
約 10 行のコードからなるメソッドがあります。コードを 1 行変更する小さな計算を除いて、まったく同じことを行うメソッドをさらに作成したいと考えています。これは、関数ポインタを渡してその 1 行を置き換えるのに最適なアプリケーションですが、Java には関数ポインタがありません。最善の代替案は何でしょうか?
解決
匿名の内部クラス
関数を渡したいとします。 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
}
});
編集: Java 8 では、ラムダ式を使用して呼び出すことができます。
ref.takingMethod(param -> bodyExpression);
他のヒント
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 つのインスタンスはすべて、単一のクラス/ファイルで定義されます。
渡したい関数を提供するインターフェイスを作成する必要があります。例えば:
/**
* 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 を使用することもできます。また、他のさまざまなテクニックを使用してパラメーター数の「固定」を少なくすることもできますが、これは通常、型安全性とのトレードオフになります。(または、関数オブジェクトのコンストラクターをオーバーライドして、そのようにパラメータを渡すこともできます。多くのアプローチがあり、特定の状況ではより効果的に機能するものもあります)。
を使用したメソッド参照 ::
オペレーター
メソッドが引数を受け入れるメソッド参照をメソッド引数で使用できます。 機能インターフェイス. 。関数型インターフェイスは、抽象メソッドを 1 つだけ含むインターフェイスです。(関数インターフェイスには、1 つ以上のデフォルト メソッドまたは静的メソッドが含まれる場合があります。)
IntBinaryOperator
機能的なインターフェイスです。その抽象的なメソッドは、 applyAsInt
, 、2つ受け入れます int
s をパラメータとして返します。 int
. Math.max
2個も受け付けます int
s を返します 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.
これも行うことができます (一部の場合は レア 場合によっては意味があります)。問題 (そしてこれは大きな問題です) は、クラス/インターフェイスを使用する際の型安全性がすべて失われ、メソッドが存在しない場合に対処しなければならないことです。
これには、アクセス制限を無視してプライベート メソッドを呼び出すことができるという「利点」があります (例には示されていませんが、コンパイラーが通常呼び出すことを許可しないメソッドを呼び出すことができます)。
繰り返しますが、これが意味のあるケースはまれですが、そのような場合には、あると便利なツールです。
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 行だけある場合は、フラグや、どちらかの行を呼び出す if(flag) ステートメントなどのパラメータを追加できます。
また、クロージャに関連する Java 7 で進行中の作業について知りたいかもしれません。
Java におけるクロージャの現状はどうなっているのでしょうか?
http://gafter.blogspot.com/2006/08/closures-for-java.html
http://tech.puredanger.com/java7/#closures
新しいJava 8 機能インターフェイス そして メソッドのリファレンス を使用して ::
オペレーター。
Java 8 は、「」を使用してメソッド参照 ( 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();
}
}
それで、ここには何があるでしょうか?
- 機能インターフェイス - これは、注釈が付けられているかどうかに関係なく、インターフェイスです。 @FunctionalInterface, 、メソッド宣言が 1 つだけ含まれています。
- メソッド参照 - これは単なる特別な構文であり、次のようになります。 オブジェクトインスタンス::メソッド名, 、それ以上でもそれ以下でもありません。
- 使用例 - 代入演算子とその後のインターフェイス メソッド呼び出しのみ。
関数型インターフェースはリスナーのみ、そしてその目的のためにのみ使用する必要があります。
他のすべてのそのような関数ポインターは、コードの可読性と理解力にとって非常に悪いからです。ただし、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 ライブラリを試してください。
追加の研究については、以下を参照してください Java 8 チートシート
そして、The Guy with The Hat に感謝します。 Java 言語仕様 §15.13 リンク。
@sblundyの答えは素晴らしいですが、匿名内部クラスには2つの小さな欠陥があります。1つは再利用できない傾向があり、2番目は構文がかさばることです。
良い点は、彼のパターンがメイン クラス (計算を実行するクラス) を変更することなく完全なクラスに拡張されることです。
新しいクラスをインスタンス化するとき、方程式の定数として機能するパラメーターをそのクラスに渡すことができます。つまり、内部クラスの 1 つが次のようになっているとします。
f(x,y)=x*y
しかし、時には次のようなものが必要になることがあります。
f(x,y)=x*y*2
そしておそらく 3 番目は次のとおりです。
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);
しかし、不変クラスの方が安全です。このようなクラスを変更可能にする正当な理由が思いつきません。
私が実際にこれを投稿したのは、匿名の内部クラスを聞くたびにうんざりするからです。プログラマーが最初にやったのは、実際のクラスを使用すべきであったのに匿名にすることであり、「必須」である冗長なコードをたくさん見てきました。彼の決断を考え直した。
戦略パターンのように思えます。Fluffycat.com の Java パターンを確認してください。
Java でプログラミングするときに本当に見逃してしまうものの 1 つは、関数のコールバックです。これらの必要性が常に存在する状況の 1 つは、項目ごとに特定のアクションを実行する必要がある階層を再帰的に処理する場合です。ディレクトリ ツリーをたどったり、データ構造を処理したりするようなものです。私の中のミニマリストは、インターフェイスを定義してから、特定のケースごとに実装を定義する必要があることを嫌います。
ある日、私はなぜそうしないのかと疑問に思いました。メソッド ポインター、つまり Method オブジェクトがあります。JIT コンパイラを最適化することで、リフレクティブ呼び出しによるパフォーマンスの大幅な低下がなくなりました。そして、たとえばファイルをある場所から別の場所にコピーすることに加えて、反映されたメソッド呼び出しのコストは取るに足らないものになります。
さらに考えてみると、OOP パラダイムのコールバックにはオブジェクトとメソッドをバインドする必要があることがわかりました。つまり、Callback オブジェクトに入ります。
私のリフレクションベースのソリューションをチェックしてください Java のコールバック. 。どのような用途でも無料です。
OK、このスレッドはすでに十分古いので、 おそらく 私の答えは質問の役に立ちません。しかし、このスレッドが解決策を見つけるのに役立ったので、とにかくここに公開します。
既知の入力と既知の出力 (両方とも) を持つ変数静的メソッドを使用する必要がありました。 ダブル)。したがって、メソッドのパッケージと名前がわかれば、次のように作業できます。
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);
ここで、 activity は任意の double 値です。SoftwareMonkey がやったように、これをもう少し抽象化して一般化しようと考えていますが、現時点ではこのままで十分満足しています。コードは 3 行で、クラスやインターフェイスは必要ありません。これはそれほど悪くありません。
関数の配列に対してインターフェイスを使用せずに同じことを行うには、次のようにします。
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); } }
ラムダジをチェックしてみよう
http://code.google.com/p/lambdaj/
特にその新しいクロージャ機能
http://code.google.com/p/lambdaj/wiki/Closures
意味のないインターフェイスを作成したり、醜い内部クラスを使用したりせずに、クロージャや関数ポインタを定義する非常に読みやすい方法が見つかります。
うわー、Java ですでに作成したことを考えると、それほど難しくない Delegate クラスを作成して、それを使用して T が戻り値の型であるパラメーターを渡すのはどうでしょうか。申し訳ありませんが、Java を学んだばかりの C++/C# プログラマとしては、関数ポインタが非常に便利なので必要です。メソッド情報を扱うクラスに精通している場合は、それを行うことができます。Java ライブラリでは、java.lang.reflect.method になります。
常にインターフェイスを使用する場合は、常にそれを実装する必要があります。イベント処理では、ハンドラーのリストへの登録/登録解除を回避するより良い方法はありませんが、値の型ではなく関数を渡す必要があるデリゲートの場合は、インターフェイスのアウトクラスに対してそれを処理するデリゲート クラスを作成します。
Java 8 の回答には、完全でまとまった例が示されていないため、ここで紹介します。
「関数ポインタ」を受け取るメソッドを次のように宣言します。
void doCalculation(Function<Integer, String> calculation, int parameter) {
final String result = calculation.apply(parameter);
}
関数にラムダ式を指定して呼び出します。
doCalculation((i) -> i.toString(), 2);
Scheme のように、動作を定義するために 1 つのパラメーター セットを受け取り、実行する別のパラメーター セットを受け取る関数を渡すのに苦労している人がいる場合は、次のようにします。
(define (function scalar1 scalar2)
(lambda (x) (* x scalar1 scalar2)))
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
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);