Javaメソッドでコールバック関数を渡す方法はありますか?
私が模倣しようとしている動作は、.Net デリゲートが関数に渡されることです。
別のオブジェクトを作成することを提案している人を見てきましたが、それはやり過ぎのように思えますが、やり過ぎが唯一の方法であることを認識しています。
Javaメソッドでコールバック関数を渡す方法はありますか?
私が模倣しようとしている動作は、.Net デリゲートが関数に渡されることです。
別のオブジェクトを作成することを提案している人を見てきましたが、それはやり過ぎのように思えますが、やり過ぎが唯一の方法であることを認識しています。
.NET 匿名デリゲートのようなものであれば、Java の匿名クラスも使用できると思います。
public class Main {
public interface Visitor{
int doJob(int a, int b);
}
public static void main(String[] args) {
Visitor adder = new Visitor(){
public int doJob(int a, int b) {
return a + b;
}
};
Visitor multiplier = new Visitor(){
public int doJob(int a, int b) {
return a*b;
}
};
System.out.println(adder.doJob(10, 20));
System.out.println(multiplier.doJob(10, 20));
}
}
Java 8 以降、ラムダ参照とメソッド参照があります。
たとえば、機能的なインターフェイスが必要なA -> B
場合は、次を使用できます。
import java.util.function.Function;
public MyClass {
public static String applyFunction(String name, Function<String,String> function){
return function.apply(name);
}
}
そして、これを呼び出す方法は次のとおりです。
MyClass.applyFunction("42", str -> "the answer is: " + str);
// returns "the answer is: 42"
また、クラスメソッドを渡すこともできます。例えば:
@Value // lombok
public class PrefixAppender {
private String prefix;
public String addPrefix(String suffix){
return prefix +":"+suffix;
}
}
次に、次のことができます。
PrefixAppender prefixAppender= new PrefixAppender("prefix");
MyClass.applyFunction("some text", prefixAppender::addPrefix);
// returns "prefix:some text"
注:
ここでは関数型インターフェースを使用しFunction<A,B>
ましたが、パッケージには他にもたくさんありますjava.util.function
。最も注目すべきものは
Supplier
:void -> A
Consumer
:A -> void
BiConsumer
:(A,B) -> void
Function
:A -> B
BiFunction
:(A,B) -> C
入出力タイプのいくつかを専門とする他の多くのもの。次に、必要なものが提供されない場合は、独自のものを作成できますFunctionalInterface
。
@FunctionalInterface
interface Function3<In1, In2, In3, Out> { // (In1,In2,In3) -> Out
public Out apply(In1 in1, In2 in2, In3 in3);
}
使用例:
String computeAnswer(Function3<String, Integer, Integer, String> f){
return f.apply("6x9=", 6, 9);
}
computeAnswer((question, a, b) -> question + "42");
// "6*9=42"
また、スローされた例外でそれを行うこともできます:
@FunctionalInterface
interface FallibleFunction<In, Out, Ex extends Exception> {
Out get(In input) throws Ex;
}
public <Ex extends IOException> String yo(FallibleFunction<Integer, String, Ex> f) throws Ex {
return f.get(42);
}
少しつまらない:
別のオブジェクトを作成することを提案している人がいるようですが、それはやり過ぎのようです
コールバックを渡すには、ほとんどすべての OO 言語で別のオブジェクトを作成する必要があるため、やり過ぎとは考えられません。おそらくあなたが言いたいのは、Java では別のクラスを作成する必要があるということです。これは、明示的なファーストクラスの関数またはクロージャーを備えた言語よりも冗長 (およびリソース集約型) です。ただし、匿名クラスは少なくとも冗長性を減らし、インラインで使用できます。
リフレクト ライブラリを使用して実装するというアイデアは興味深いものであり、非常にうまく機能すると思いました。唯一の欠点は、有効なパラメーターを渡しているというコンパイル時のチェックが失われることです。
public class CallBack {
private String methodName;
private Object scope;
public CallBack(Object scope, String methodName) {
this.methodName = methodName;
this.scope = scope;
}
public Object invoke(Object... parameters) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Method method = scope.getClass().getMethod(methodName, getParameterClasses(parameters));
return method.invoke(scope, parameters);
}
private Class[] getParameterClasses(Object... parameters) {
Class[] classes = new Class[parameters.length];
for (int i=0; i < classes.length; i++) {
classes[i] = parameters[i].getClass();
}
return classes;
}
}
このように使用します
public class CallBackTest {
@Test
public void testCallBack() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
TestClass testClass = new TestClass();
CallBack callBack = new CallBack(testClass, "hello");
callBack.invoke();
callBack.invoke("Fred");
}
public class TestClass {
public void hello() {
System.out.println("Hello World");
}
public void hello(String name) {
System.out.println("Hello " + name);
}
}
}
メソッドは (まだ) Java の第一級オブジェクトではありません。関数ポインタをコールバックとして渡すことはできません。代わりに、必要なメソッドを含むオブジェクト (通常はインターフェイスを実装する) を作成して渡します。
探している動作を提供する Java のクロージャーの提案がなされましたが、次の Java 7 リリースには含まれません。
lambdaj ライブラリでクロージャがどのように実装されているかを確認してください。実際には、C# デリゲートと非常によく似た動作をします。
次のパターンCallback
を使用して実行することもできます。Delegate
Callback.java
public interface Callback {
void onItemSelected(int position);
}
PagerActivity.java
public class PagerActivity implements Callback {
CustomPagerAdapter mPagerAdapter;
public PagerActivity() {
mPagerAdapter = new CustomPagerAdapter(this);
}
@Override
public void onItemSelected(int position) {
// Do something
System.out.println("Item " + postion + " selected")
}
}
CustomPagerAdapter.java
public class CustomPagerAdapter {
private static final int DEFAULT_POSITION = 1;
public CustomPagerAdapter(Callback callback) {
callback.onItemSelected(DEFAULT_POSITION);
}
}
java.lang.reflectを使用して「コールバック」を実装してみました。サンプルは次のとおりです。
package StackOverflowQ443708_JavaCallBackTest;
import java.lang.reflect.*;
import java.util.concurrent.*;
class MyTimer
{
ExecutorService EXE =
//Executors.newCachedThreadPool ();
Executors.newSingleThreadExecutor ();
public static void PrintLine ()
{
System.out.println ("--------------------------------------------------------------------------------");
}
public void SetTimer (final int timeout, final Object obj, final String methodName, final Object... args)
{
SetTimer (timeout, obj, false, methodName, args);
}
public void SetTimer (final int timeout, final Object obj, final boolean isStatic, final String methodName, final Object... args)
{
Class<?>[] argTypes = null;
if (args != null)
{
argTypes = new Class<?> [args.length];
for (int i=0; i<args.length; i++)
{
argTypes[i] = args[i].getClass ();
}
}
SetTimer (timeout, obj, isStatic, methodName, argTypes, args);
}
public void SetTimer (final int timeout, final Object obj, final String methodName, final Class<?>[] argTypes, final Object... args)
{
SetTimer (timeout, obj, false, methodName, argTypes, args);
}
public void SetTimer (final int timeout, final Object obj, final boolean isStatic, final String methodName, final Class<?>[] argTypes, final Object... args)
{
EXE.execute (
new Runnable()
{
public void run ()
{
Class<?> c;
Method method;
try
{
if (isStatic) c = (Class<?>)obj;
else c = obj.getClass ();
System.out.println ("Wait for " + timeout + " seconds to invoke " + c.getSimpleName () + "::[" + methodName + "]");
TimeUnit.SECONDS.sleep (timeout);
System.out.println ();
System.out.println ("invoking " + c.getSimpleName () + "::[" + methodName + "]...");
PrintLine ();
method = c.getDeclaredMethod (methodName, argTypes);
method.invoke (obj, args);
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
PrintLine ();
}
}
}
);
}
public void ShutdownTimer ()
{
EXE.shutdown ();
}
}
public class CallBackTest
{
public void onUserTimeout ()
{
System.out.println ("onUserTimeout");
}
public void onTestEnd ()
{
System.out.println ("onTestEnd");
}
public void NullParameterTest (String sParam, int iParam)
{
System.out.println ("NullParameterTest: String parameter=" + sParam + ", int parameter=" + iParam);
}
public static void main (String[] args)
{
CallBackTest test = new CallBackTest ();
MyTimer timer = new MyTimer ();
timer.SetTimer ((int)(Math.random ()*10), test, "onUserTimeout");
timer.SetTimer ((int)(Math.random ()*10), test, "onTestEnd");
timer.SetTimer ((int)(Math.random ()*10), test, "A-Method-Which-Is-Not-Exists"); // java.lang.NoSuchMethodException
timer.SetTimer ((int)(Math.random ()*10), System.out, "println", "this is an argument of System.out.println() which is called by timer");
timer.SetTimer ((int)(Math.random ()*10), System.class, true, "currentTimeMillis");
timer.SetTimer ((int)(Math.random ()*10), System.class, true, "currentTimeMillis", "Should-Not-Pass-Arguments"); // java.lang.NoSuchMethodException
timer.SetTimer ((int)(Math.random ()*10), String.class, true, "format", "%d %X", 100, 200); // java.lang.NoSuchMethodException
timer.SetTimer ((int)(Math.random ()*10), String.class, true, "format", "%d %X", new Object[]{100, 200});
timer.SetTimer ((int)(Math.random ()*10), test, "NullParameterTest", new Class<?>[]{String.class, int.class}, null, 888);
timer.ShutdownTimer ();
}
}
それは少し古いですが、それにもかかわらず...ピーター・ウィルキンソンの答えは、int / Integerのようなプリミティブ型では機能しないという事実を除いて、素晴らしいと思いました。問題は.getClass()
for でありparameters[i]
、これは for インスタンスを返しますが、これは(Java の障害)java.lang.Integer
によって正しく解釈されません...getMethod(methodName,parameters[])
私はそれを Daniel Spiewak の提案 (これに対する彼の回答) と組み合わせました。成功へのステップは次のとおりです: キャッチNoSuchMethodException
-> getMethods()
-> 一致するものを探しますmethod.getName()
-> 次に、パラメーターのリストを明示的にループし、Daniels ソリューションを適用します。たとえば、型の一致と署名の一致を識別します。
私は最近、次のようなことを始めました:
public class Main {
@FunctionalInterface
public interface NotDotNetDelegate {
int doSomething(int a, int b);
}
public static void main(String[] args) {
// in java 8 (lambdas):
System.out.println(functionThatTakesDelegate((a, b) -> {return a*b;} , 10, 20));
}
public static int functionThatTakesDelegate(NotDotNetDelegate del, int a, int b) {
// ...
return del.doSomething(a, b);
}
}