콜백은 Wikipedia에서 다음과 같이 정의됩니다.
컴퓨터 프로그래밍에서 콜백 함수는 함수 매개변수를 통해 다른 코드에 전달되는 특정 실행 코드 블록에 대한 참조를 의미합니다.
그 목적은 하위 수준 코드가 상위 수준에서 정의된 서브루틴을 호출할 수 있도록 하는 것입니다.
예를 들어보면 더 명확해질 수 있습니다. Android에서 네트워크 요청을 만들기 위해 개조를 사용하는 것을 예로 들어 보겠습니다. 이는 비동기 콜백의 예입니다.
네트워크 요청을 시작한 후 앱은 다른 작업을 계속할 수 있습니다. 네트워크 요청 결과는 일반적으로 onResponse 및 onFailure 메서드를 통해 반환됩니다. 코드의 관련 부분을 살펴보세요:
call.enqueue(new Callback<HistoryBean>() { @Override public void onResponse(Call<HistoryBean> call, Response<HistoryBean> response) { HistoryBean hb = response.body(); if(hb == null) return; showText.append(hb.isError() + ""); for(HistoryBean.ResultsBean rb : hb.getResults()){ showText.append(rb.getTitle() + "/n"); } } @Override public void onFailure(Call<HistoryBean> call, Throwable t) { } });
위의 CallBack에서 제네릭을 무시하세요. Wikipedia의 정의에 따르면 익명 내부 클래스의 모든 코드는 다른 코드에 전달되는 함수 매개변수로 간주될 수 있습니다. . 특정 블록은 실행 코드에 대한 참조가 될 수 있습니다. onResponse 및 onFailure 두 가지 메서드는 콜백 메서드입니다. 기본 코드는 기존에 변경되지 않은 네트워크 요청 부분이고, 상위 수준에서 정의한 서브루틴은 콜백이며 구체적인 구현은 사용자에게 맡기므로 유연성이 높습니다. 위의 내용은 enqueue(Callback callback) 메소드를 통해 관련됩니다.
콜백 방법의 단계
위에서 언급한 콜백은 프로그램 작성에 적용하면 다음과 같이 말할 수 있습니다.
수업 시간에 A를 호출합니다. B에서 A의 특정 메서드 C로 이동한 후 클래스 B가 차례로 클래스 A의 메서드 D를 호출합니다. 여기서 D는 콜백 메서드입니다. 클래스 B는 하위 수준 코드이고 클래스 A는 상위 수준 코드입니다.
위의 설명을 통해 우리는 D 메소드의 다양성을 표현하기 위해 인터페이스 형식을 사용하여 클래스 B가 원하는 경우를 인터페이스 메소드로 호출합니다. 클래스 A 메서드 D를 호출하면 클래스 A는 이 인터페이스를 구현해야 합니다. 이러한 방식으로 구현에 따라 다형성이 발생하여 메서드가 유연해집니다.
클래스 A가 클래스 B의 특정 메서드 C를 호출하려는 경우 클래스 A는 B에 대한 참조를 포함해야 하며, 그렇지 않으면 호출할 수 없습니다. 이 단계를 콜백 인터페이스 등록이라고 합니다. 그렇다면 클래스 B를 구현하여 클래스 A의 메서드 D를 직접 호출하려면 어떻게 해야 할까요? 클래스 B의 메서드 C는 인터페이스 유형의 매개변수를 허용하므로 메서드 C에서만 이 인터페이스 유형을 사용해야 합니다. 매개 변수를 사용하여 메서드 D를 호출하려면 클래스 B의 클래스 A에서 메서드 D를 호출하면 됩니다. 이 단계를 콜백 인터페이스 호출이라고 합니다.
이는 클래스 B의 메서드 C가 차례로 클래스 A의 메서드 D를 호출해야 한다는 의미이기도 합니다. 이것이 바로 콜백입니다. B를 호출하는 것은 기본 API를 사용하는 고급 코드로 볼 수 있는 직접 호출입니다. 우리는 종종 이런 방식으로 프로그램을 작성합니다. B가 A를 호출하는 것은 콜백이며 기본 API를 실행하려면 고급 코드가 필요합니다.
마지막으로 콜백 메서드의 단계를 요약하면 다음과 같습니다.
클래스 A는 인터페이스 콜백 콜백을 구현합니다.
클래스 A는 B에 대한 참조를 포함합니다.
B CallBack 매개변수를 갖는 메소드 f(CallBack 콜백)가 있습니다
클래스 A에 B의 메소드 f(CallBack 콜백) 호출 - 콜백 인터페이스 등록
B는 f에서 호출할 수 있습니다. (CallBack 콜백에서 A를 호출하는 방식) 메소드 - 콜백 인터페이스 호출
콜백 예시
아들이 게임을 하고 엄마가 식사 준비를 기다립니다.
위의 예에서는 아들이 콜백 인터페이스를 구현해야 하고 어머니가 콜백 인터페이스를 호출해야 한다는 것이 분명합니다. 따라서 먼저 콜백 인터페이스를 정의한 다음 아들이 이 콜백 인터페이스를 구현하도록 합니다.
코드는 다음과 같습니다.
public interface CallBack { void eat(); }
public class Son implements CallBack{ private Mom mom; //A类持有对B类的引用 public void setMom(Mom mom){ this.mom = mom; } @Override public void eat() { System.out.println("我来吃饭了"); } public void askMom(){ //通过B类的引用调用含有接口参数的方法。 System.out.println("饭做了吗?"); System.out.println("没做好,我玩游戏了"); new Thread(() -> mom.doCook(Son.this)).start(); System.out.println("玩游戏了中......"); } }
그런 다음 인터페이스 매개변수가 있는 doCook
public class Mom { //在含有接口参数的方法中利用接口参数调用回调方法 public void doCook(CallBack callBack){ new Thread(new Runnable() { @Override public void run() { try { System.out.println("做饭中......"); Thread.sleep(5000); callBack.eat(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
메소드가 있는 상위 클래스도 정의해야 합니다.
테스트 클래스를 통과합니다.
public class Test { public static void main(String[] args) { Mom mom = new Mom(); Son son = new Son(); son.setMom(mom); son.askMom(); } }
이 예는 일반적인 콜백 예입니다. Son 클래스는 인터페이스의 콜백 메소드를 구현합니다. AskMom 메소드를 통해 Mom 클래스에서 doCook을 호출하여 등록 콜백 인터페이스를 구현합니다. 이는 클래스 B를 호출하는 클래스 A의 코드 C와 동일합니다. Mom 클래스의 doCook은 Son 클래스의 eat을 다시 호출하여 Son 클래스에 결과를 알려줍니다.
이런 식으로 정의에 맞는 간단한 콜백을 구현했습니다.
콜백 예제 추가 탐색
주로 Son 클래스의 코드를 살펴보겠습니다.
public class Son implements CallBack{ public Mom mom; public Son(Mom mom){ this.mom = mom; } public void askMom(){ System.out.println("饭做了吗?"); System.out.println("没做好,我玩游戏了"); new Thread(() -> mom.doCook(Son.this)).start(); System.out.println("玩游戏了中......"); } @Override public void eat() { System.out.println("好了,我来吃饭了"); } }
이 클래스에서는 , Except 몇 가지 명령문을 출력하는 것 외에도 정말 유용한 부분은 mom.doCook(Son.this) 및 eat 메소드를 재정의하는 것입니다. 따라서 이 콜백을 익명 내부 클래스 형태로 축약할 수 있습니다. 코드는 다음과 같습니다.
public class CallBackTest { public static void main(String[] args) { Mom mom = new Mom(); new Thread(()-> mom.doCook(() -> System.out.println("吃饭了......"))).start(); } }
Son 클래스를 취소하고 main 메소드의 익명 내부 클래스를 통해 직접 eat 메소드를 구현합니다. 실제로 익명 내부 클래스는 콜백의 구현입니다.
비동기 콜백과 동기 콜백
콜백 위에서 말했듯이 A는 클래스 B의 메소드 C를 호출한 후, 메소드 C의 클래스 A의 객체를 통해 클래스 A의 메소드 D를 호출합니다.
비동기와 동기화에 대해 먼저 이야기해 보겠습니다.
동기화
동기화는 이전 메서드 호출이 이루어지지 않은 경우 메서드를 호출하는 것을 말합니다. 완료되었습니다. 새 메소드 호출을 수행할 수 없습니다. 즉, 이전 작업을 완료한 후에야 다음 작업을 수행할 수 있습니다.
비동기
동기화와 비교하여 비동기는 새 메서드를 호출하기 전에 이전 메서드 호출이 끝날 때까지 기다릴 필요가 없습니다. 따라서 비동기식 메서드 호출에서는 메서드 호출 결과를 사용자에게 알려주는 메서드가 필요합니다.
비동기 구현 방법
在Java中最常实现的异步方式就是让你想异步的方法在一个新线程中执行。
我们会发现一点,异步方法调用中需要一个方法来通知使用者调用结果,结合上面所讲,我们会发现回调方法就适合做这个事情,通过回调方法来通知使用者调用的结果。
那异步回调就是A调用B的方法C时是在一个新线程当中去做的。
上面的母亲通知儿子吃饭的例子,就是一个异步回调的例子。在一个新线程中,调用doCook方法,最后通过eat来接受返回值,当然使用lamdba优化之后的,本质是一样的。
同步回调就是A调用B的方法C没有在一个新线程,在执行这个方法C的时候,我们什么都不能做,只能等待他执行完成。
同步回调与异步回调的例子
我们看一个Android中的一个同步回调的例子:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i("button","被点击"); } });
button通过setOnClickListener注册回调函数,和上面写的一样,通过匿名内部类的形式将接口的引用传进去。由于button调用setOnClickListener没有新建一个线程,所以这个是同步的回调。
而异步回调,就是我们开篇讲的那个例子:
call.enqueue(new Callback<HistoryBean>() { @Override public void onResponse(Call<HistoryBean> call, Response<HistoryBean> response) { HistoryBean hb = response.body(); if(hb == null) return; showText.append(hb.isError() + ""); for(HistoryBean.ResultsBean rb : hb.getResults()){ showText.append(rb.getTitle() + "/n"); } } @Override public void onFailure(Call<HistoryBean> call, Throwable t) { } });
这个enqueue方法是一个异步方法去请求远程的网络数据。其内部实现的时候是通过一个新线程去执行的。
通过这两个例子,我们可以看出同步回调与异步回调的使用其实是根据不同的需求而设计。不能说一种取代另一种,像上面的按钮点击事件中,如果是异步回调,用户点击按钮之后其点击效果不是马上出现,而用户又不会执行其他操作,那么会感觉很奇怪。而像网络请求的异步回调,因为受限于请求资源可能不存在,网络连接不稳定等等原因导致用户不清楚方法执行的时候,所以会用异步回调,发起方法调用之后去做其他事情,然后等回调的通知。
回调方法在通信中的应用
上面提到的回调方法,除了网络请求框架的回调除外,其回调方法都是没有参数,下面,我们看一下在回调方法中加入参数来实现一些通信问题。
如果我们想要A类得到B类经过一系列计算,处理后数据,而且两个类是不能通过简单的将B的引用给A类就可以得到数据的。我们可以考虑回调。
步骤如下:
在拥有数据的那个类里面写一个回调的接口。-->这里就是B类中写一个回调接口
回调方法接收一个参数,这个参数就是要得到的数据
同样是在这个类里写一个注册回调的方法。
在注册回调方法,用接口的引用去调用回调接口,把B类的数据当做参数传入回调的方法中。
在A类中,用B类的引用去注册回调接口,把B类中的数据通过回调传到A类中。
上面说的步骤,有点抽象。下面我们看一个例子,一个是Client,一个是Server。Client去请求Server经过耗时处理后的数据。
public class Client{ public Server server; public String request; //链接Server,得到Server引用。 public Client connect(Server server){ this.server = server; return this; } //Client,设置request public Client setRequest(String request){ this.request = request; return this; } //异步发送请求的方法,lamdba表达式。 public void enqueue(Server.CallBack callBack){ new Thread(()->server.setCallBack(request,callBack)).start(); } }
public class Server { public String response = "这是一个html"; //注册回调接口的方法,把数据通过参数传给回调接口 public void setCallBack(String request,CallBack callBack){ System.out.println("已经收到request,正在计算当中......"); new Thread(() -> { try { Thread.sleep(5000); callBack.onResponse(request + response); } catch (InterruptedException e) { e.printStackTrace(); callBack.onFail(e); } }).start(); } //在拥有数据的那个类里面写一个接口 public interface CallBack{ void onResponse(String response); void onFail(Throwable throwable); } }
接下来,我们看一下测试的例子:
public class CallBackTest { public static void main(String[] args) { Client client = new Client(); client.connect(new Server()).setRequest("这个文件是什么?").enqueue(new Server.CallBack() { @Override public void onResponse(String response) { System.out.println(response); } @Override public void onFail(Throwable throwable) { System.out.println(throwable.getMessage()); } }); } }
结果如下:
已经收到request,正在计算当中...... 这个文件是什么?这是一个html
以上就是通过回调的方式进行通信。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持PHP中文网!
更多Java回调方法详解相关文章请关注PHP中文网!