Javaメソッドでコールバック関数を渡す方法はありますか?
私が模倣しようとしている動作は、関数に渡される.Net Delegateです。
別のオブジェクトを作成することを提案している人を見たことがありますが、それはやり過ぎのようですが、やり過ぎが唯一の方法であることは知っています。
Javaメソッドでコールバック関数を渡す方法はありますか?
私が模倣しようとしている動作は、関数に渡される.Net Delegateです。
別のオブジェクトを作成することを提案している人を見たことがありますが、それはやり過ぎのようですが、やり過ぎが唯一の方法であることは知っています。
回答:
.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
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"
java.util.function
あなたが探しているものであるなら、あなたは行ってもいいです。次に、I / Oのジェネリックを操作できます。(?)
java.util.function
は十分ではないため、独自の機能インターフェースを作成する必要があるということです。
簡単にするために、Runnableを使用できます。
private void runCallback(Runnable callback)
{
// Run callback
callback.run();
}
使用法:
runCallback(new Runnable()
{
@Override
public void run()
{
// Running callback
}
});
public interface SimpleCallback { void callback(Object... objects); }
これは非常に簡単で、いくつかのパラメーターを渡す必要がある場合にも役立ちます。
少しつまらない:
別のオブジェクトを作成することを提案している人がいるようですが、やり過ぎです
コールバックを渡すことには、ほぼすべてのOO言語で個別のオブジェクトを作成することが含まれるため、過剰と見なすことはほとんどできません。おそらくあなたが意味することは、Javaでは別のクラスを作成する必要があるということです。これは、明示的なファーストクラスの関数またはクロージャーを使用する言語よりも冗長(そしてリソース集約的)です。ただし、匿名クラスは少なくとも冗長性を減らし、インラインで使用できます。
それでも、私が探していた方法である最も好ましい方法があることがわかります。それは基本的にこれらの回答から派生したものですが、より冗長で効率的な方法で操作する必要がありました。 と、私は誰もが私が思い付くものを探していると思いますが、
まず、インターフェースをシンプルにする
public interface myCallback {
void onSuccess();
void onError(String err);
}
結果を処理するために実行したいときにこのコールバックを実行するようにします- おそらく非同期呼び出しの後、これらの再利用に依存するいくつかのものを実行したい
// import the Interface class here
public class App {
public static void main(String[] args) {
// call your method
doSomething("list your Params", new myCallback(){
@Override
public void onSuccess() {
// no errors
System.out.println("Done");
}
@Override
public void onError(String err) {
// error happen
System.out.println(err);
}
});
}
private void doSomething(String param, // some params..
myCallback callback) {
// now call onSuccess whenever you want if results are ready
if(results_success)
callback.onSuccess();
else
callback.onError(someError);
}
}
doSomething
結果が来たときに通知するためにコールバックを追加したい場合に少し時間がかかる関数です。このメソッドにパラメーターとしてコールバックインターフェイスを追加します
これは、ラムダを使用するJava 8では非常に簡単です。
public interface Callback {
void callback();
}
public class Main {
public static void main(String[] args) {
methodThatExpectsACallback(() -> System.out.println("I am the callback."));
}
private static void methodThatExpectsACallback(Callback callback){
System.out.println("I am the method.");
callback.callback();
}
}
リフレクトライブラリを使用して実装するというアイデアは興味深いと思い、これはかなりうまくいくと思います。唯一の欠点は、有効なパラメータを渡しているかどうかのコンパイル時チェックが失われることです。
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リリースには含まれません。
Javaでこの種の機能が必要な場合は、通常、Observerパターンを使用します。これは追加のオブジェクトを意味しますが、コードを読みやすくするために広く理解されているパターンであり、広く理解されているパターンだと思います。
lambdajライブラリーでどのように実装されているかをクロージャーで確認してください。実際には、C#デリゲートと非常によく似た動作をします。
私は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 ();
}
}
timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});
。出力はnull test: [null]
args[i].getClass()
か?私のポイントは、引数の型に基づいてメソッドを選択すると機能しないことです。で動作しString.format
ますが、受け入れる他のものでは動作しない場合がありますnull
。
argTypes
配列を手動で渡すことができる関数を追加したので、null
NullPointerExceptionが発生することなく引数/パラメーターを渡すことができます。出力例:NullParameterTest: String parameter=null, int parameter=888
次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);
}
}
私は最近このようなことを始めました:
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);
}
}
少し古いですが、それでも... int / Integerのようなプリミティブ型では機能しないという点を除いて、Peter Wilkinsonの答えは素晴らしいと思いました。問題はの.getClass()
forでありparameters[i]
、たとえばを返しますがjava.lang.Integer
、これはによって正しく解釈されません。getMethod(methodName,parameters[])
(Javaのエラー)では ...
私はそれをダニエル・スピーワクの提案と組み合わせました(これに対する彼の答え。成功へのステップには、キャッチNoSuchMethodException
-> getMethods()
->一致するものを->で探してmethod.getName()
から、パラメーターのリストを明示的にループし、型の一致と署名の一致を識別するようなDanielsソリューションを適用しました。
抽象クラスを使用すると、次のようにエレガントになります。
// Something.java
public abstract class Something {
public abstract void test();
public void usingCallback() {
System.out.println("This is before callback method");
test();
System.out.println("This is after callback method");
}
}
// CallbackTest.java
public class CallbackTest extends Something {
@Override
public void test() {
System.out.println("This is inside CallbackTest!");
}
public static void main(String[] args) {
CallbackTest myTest = new CallbackTest();
myTest.usingCallback();
}
}
/*
Output:
This is before callback method
This is inside CallbackTest!
This is after callback method
*/
object
別のユーザーに「通知」するように設計されているobject
ため、2番目object
はイベントを処理できます。あなたabstract class
は決して別々object
ではありません、それはただ別々class
です。1つだけを使用して、さまざまな機能を実行するために異なるものobject
を取得classes
しています。これは、ポリモーフィズムの本質であり、コールバックパターンではありません。
public class HelloWorldAnonymousClasses {
//this is an interface with only one method
interface HelloWorld {
public void printSomething(String something);
}
//this is a simple function called from main()
public void sayHello() {
//this is an object with interface reference followed by the definition of the interface itself
new HelloWorld() {
public void printSomething(String something) {
System.out.println("Hello " + something);
}
}.printSomething("Abhi");
//imagine this as an object which is calling the function'printSomething()"
}
public static void main(String... args) {
HelloWorldAnonymousClasses myApp =
new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}
//Output is "Hello Abhi"
基本的に、インターフェイスのオブジェクトを作成する場合、インターフェイスはオブジェクトを持つことができないため、それは不可能です。
オプションは、一部のクラスにインターフェイスを実装させ、そのクラスのオブジェクトを使用してその関数を呼び出すことです。しかし、このアプローチは本当に冗長です。
または、新しいHelloWorld()(*これはクラスではなくインターフェイスであることを確認してください)を記述してから、インターフェイスメソッド自体の定義をフォローアップします。(*この定義は実際には匿名クラスです)。次に、メソッド自体を呼び出すことができるオブジェクト参照を取得します。
インターフェイスを作成し、コールバッククラスに同じインターフェイスプロパティを作成します。
interface dataFetchDelegate {
void didFetchdata(String data);
}
//callback class
public class BackendManager{
public dataFetchDelegate Delegate;
public void getData() {
//Do something, Http calls/ Any other work
Delegate.didFetchdata("this is callbackdata");
}
}
コールバックしたいクラスで、上記のCreated Interfaceを実装します。また、呼び出されるクラスの「this」オブジェクト/参照を渡します。
public class Main implements dataFetchDelegate
{
public static void main( String[] args )
{
new Main().getDatafromBackend();
}
public void getDatafromBackend() {
BackendManager inc = new BackendManager();
//Pass this object as reference.in this Scenario this is Main Object
inc.Delegate = this;
//make call
inc.getData();
}
//This method is called after task/Code Completion
public void didFetchdata(String callbackData) {
// TODO Auto-generated method stub
System.out.println(callbackData);
}
}