Java의 콜백 함수


177

Java 메소드에서 콜백 함수를 전달하는 방법이 있습니까?

내가 모방하려는 행동은 .Net Delegate가 함수에 전달되는 것입니다.

사람들이 별도의 객체를 만들 것을 제안하는 것을 보았지만 과잉으로 보이지만 때로는 과잉이 유일한 방법이라는 것을 알고 있습니다.


51
Java가 작동하지 않기 때문에 과잉입니다 (펑크해야합니다 ...).
Keith Pinson

또 다른 자바 8 예제 : stackoverflow.com/questions/14319787/…
Vadzim

답변:


155

.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));

    }
}

3
이것은 Java 1.0 이후의 표준 방법입니다.
찰리 마틴

1
나는 이것을 잘 알고 있었고, 내가 원하는 것보다 엄청나게 더 장황하지만 작동합니다.
Omar Kooheji

23
@Omar는 동의했다. C #을 오랫동안 사용한 후에 Java로 돌아 왔고 람다 / 대의원을 그리워했습니다. 자바로 오세요!
Drew Noakes

4
@DrewNoakes, 좋은 소식은, Java 8 에 람다 (대부분)가 있다는 것입니다.
Lucas

2
단일 메소드로 방문자 인터페이스를 정의 했으므로 대신 일치하는 람다를 전달할 수 있습니다.
Joey Baruch

43

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"

1
콜백의 경우 각 콜백 유형에는 일반적으로 여러 구문이 있으므로 고유 한 기능 인터페이스를 작성하는 것이 좋습니다.
Sri Harsha Chilakapati

잘 모르겠습니다. 수업 중 하나 java.util.function가 찾고있는 것이라면 갈 수 있습니다. 그런 다음 I / O의 제네릭으로 재생할 수 있습니다. (?)
Juh_

콜백 함수를 사용하는 C ++ 코드를 Java 8로 변환하는 것에 대해 말하지 않았습니다. 실제로 더 많은 매개 변수가 있기 때문에 각각의 고유 한 함수 포인터에 대해 Java에서 Functional Interface를 작성해야합니다. 생산 코드.
Sri Harsha Chilakapati

2
내 결론은 대부분의 경우 기본 제공되는 인터페이스로 java.util.function는 충분하지 않기 때문에 자체 기능 인터페이스를 작성해야한다는 것 입니다.
Sri Harsha Chilakapati

1
기존 기능 인터페이스와 새로운 인터페이스 생성 방법에 대한 메모를 추가했습니다.
Juh_

28

간단히하기 위해 Runnable을 사용할 수 있습니다 .

private void runCallback(Runnable callback)
{
    // Run callback
    callback.run();
}

용법:

runCallback(new Runnable()
{
    @Override
    public void run()
    {
        // Running callback
    }
});

2
간단한 콜백을 수행하기 위해 새 인터페이스 또는 클래스를 만들 필요가 없기 때문에 이것을 좋아합니다. 팁 고마워!
브루스

1
public interface SimpleCallback { void callback(Object... objects); }이것은 매우 간단하며 일부 매개 변수를 전달해야 할 수도 있습니다.
Marco C.

@cprcrack run () 함수에서 값을 전달할 방법이 없습니까?
Chris Chevalier

16

조금 따끔 따끔 :

사람들이 별도의 객체를 만들 것을 제안하는 것처럼 보였지만 과잉으로 보입니다.

콜백을 전달하는 것은 거의 모든 OO 언어로 별도의 객체를 생성하는 것을 포함하므로 과잉으로 간주 될 수 없습니다. 아마도 자바에서 별도의 클래스를 만들어야한다는 것입니다.이 클래스는 명시 적 일류 함수 또는 클로저가있는 언어보다 더 장황하고 자원 집약적입니다. 그러나 익명 클래스는 최소한 세부 정보를 줄이고 인라인으로 사용할 수 있습니다.


12
그렇습니다. 30 개 정도의 이벤트로 30 개의 클래스가 생깁니다.
Omar Kooheji

16

그러나 나는 그것이 기본적으로 이러한 답변에서 파생 무슨 내가 찾던 ..이었다 가장 선호하는 방법이 볼 수 있지만 나는 그것을 더 이상 중복 효율적으로 조작했다 ... 그리고 난 모든 사람들이 내가 가지고 올 것을 찾고 생각

요점 ::

먼저 간단한 인터페이스 만듭니다.

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 콜백을 추가하여 결과가 왔을 때 알리고 콜백 인터페이스를이 메소드에 매개 변수로 추가하는 데 시간이 걸리는 함수입니다.

내 요점이 분명하길 바랍니다.)


11

람다가있는 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();
    }
}

논쟁의 여지가 있다면 이런 식입니까? pastebin.com/KFFtXPNA
Nashev

1
네. 적절한 람다 구문이 유지되는 한 많은 양의 인수가 작동하거나 작동하지 않습니다.
gk bwg rhb mbreafteahbtehnb

7

리플렉션 라이브러리를 사용하여 구현하는 아이디어가 흥미 롭다는 것을 알았고 꽤 잘 작동한다고 생각했습니다. 유일한 단점은 유효한 매개 변수를 전달하고 있는지 컴파일 시간 검사를 잃는 것입니다.

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

조금 과잉처럼 보인다 (/ me ducks)
Diederik

사용 횟수와 프로젝트 규모에 따라 달라집니다. 소수의 프로젝트에는 과잉이며 큰 프로젝트에는 실용적입니다.
Juh_

또한 @monnoo의 답변을보십시오
Juh_

5

메소드는 (아직) Java에서 일류 객체가 아닙니다. 함수 포인터를 콜백으로 전달할 수 없습니다. 대신 필요한 메소드가 포함 된 오브젝트 (일반적으로 인터페이스를 구현 함)를 작성하여 전달하십시오.

찾고있는 동작을 제공하는 Java 클로저 제안이 작성되었지만 향후 Java 7 릴리스에는 포함되지 않습니다.


1
"메소드는 아직 자바에서 일류 객체가 아니다"-글쎄, 당신은 확실히 클래스를 전달할 수있는 class [1] 메소드가있다. 자바에서 기대하는 깨끗하고 관용적 인 OO 코드는 아니지만 편리 할 수 ​​있습니다. 그래도 고려해야 할 것이 있습니다. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kölker

4

Java에서 이러한 종류의 기능이 필요할 때 일반적으로 Observer 패턴을 사용합니다 . 그것은 여분의 객체를 의미하지만, 나는 그것이 깔끔한 방법이라고 생각하고 널리 이해되는 패턴이며 코드 가독성에 도움이됩니다.



3

'콜백'을 구현하기 위해 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 ();
    }
}

null을 인수로 어떻게 전달합니까?
TWiStErRob

이 샘플에서 @TWiStErRob는 다음과 같습니다 timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. 출력은null test: [null]
LiuYan 刘 研

NPE args[i].getClass()아닌가요? 내 요점은 인수 유형에 따라 메소드를 선택하면 작동하지 않는다는 것입니다. 그것은 작동String.format 하지만 받아들이는 다른 것에서는 작동하지 않을 수 있습니다 null.
TWiStErRob

@TWiStErRob, 좋은 지적입니다! argTypes배열을 수동으로 전달할 수있는 함수를 추가 했으므로 이제 nullNullPointerException없이 인수 / 매개 변수를 전달할 수 있습니다 . 샘플 출력 :NullParameterTest: String parameter=null, int parameter=888
LiuYan 刘 研 Feeb

3

패턴을 Callback사용하여 수행 할 수도 있습니다 Delegate.

콜백 .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);
    }
}

1

최근에 다음과 같은 일을 시작했습니다.

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

1

조금 낡았지만 그럼에도 불구하고 ... Peter Wilkinson의 대답은 int / Integer와 같은 기본 유형에는 작동하지 않는다는 사실을 제외하고는 좋았습니다. 문제는에 .getClass()대한 parameters[i]것인데, 예를 들어 반환 java.lang.Integer하지만 다른 한편으로는 올바르게 해석되지 않습니다.getMethod(methodName,parameters[]) (자바의 잘못) ...

나는 (다니엘 스피 웍의 제안으로 결합 이 그의 대답 ) 성공하는 단계는 다음과 같습니다. catching NoSuchMethodException-> getMethods()-> 일치하는 항목을 하나씩 찾고 method.getName()-> 매개 변수 목록을 명시 적으로 반복하고 Daniels 솔루션을 적용하여 유형 일치 및 서명 일치를 식별합니다.


0

다음과 같이 추상 클래스를 사용하는 것이 더 우아하다고 생각합니다.

// 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
*/

이것은 콜백이 아닌 다형성입니다. 콜백 패턴을 확인해야합니다.
simo.3792

네, 다형성을 사용하여 수퍼 클래스에서 콜백합니다. 다형성만으로는이 기능을 사용할 수 없습니다. 클래스 Something에있는 usingCallback ()을 호출 한 다음 사용자가 test () 함수를 작성한 서브 클래스로 다시 호출합니다.
avatar1337

2
콜백은 중요한 일이 발생했을 때 object다른 사람 에게 "알림" 하도록 설계되었으므로 object두 번째 object는 이벤트를 처리 할 수 ​​있습니다. 당신 abstract class은 결코 별개 의 것이 아니며 object단지 별개의 것 class입니다. 콜백 패턴이 아닌 다형성의 본질 인 다른 기능을 수행하기 위해 하나만 사용 object하고 classes다른 기능을 사용하고 있습니다.
simo.3792

0
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"

인터페이스의 객체를 만들 수 없기 때문에 기본적으로 인터페이스의 객체를 만들려면 불가능합니다.

옵션은 일부 클래스가 인터페이스를 구현 한 다음 해당 클래스의 객체를 사용하여 해당 함수를 호출하도록하는 것입니다. 그러나이 방법은 정말 장황합니다.

또는 new HelloWorld ()를 작성하고 (* 이것은 클래스가 아닌 인터페이스 임) 인터페이스 메소드 자체의 정의에 따라 수행하십시오. (*이 정의는 실제로 익명 클래스입니다). 그런 다음 메소드 자체를 호출 할 수있는 객체 참조를 얻습니다.


0

콜백 클래스에서 인터페이스를 만들고 동일한 인터페이스 속성을 만듭니다.

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");
   }

}

이제 다시 호출하려는 클래스에서 위의 생성 된 인터페이스를 구현하십시오. 또한 클래스의 "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);
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.