在Java中将函数作为参数传递

76
我正在熟悉Android框架和Java,并希望创建一个通用的“NetworkHelper”类,该类将处理大部分网络代码,让我能够从中调用网页。我遵循了developer.android.com上的一篇文章来创建我的网络类:http://developer.android.com/training/basics/network-ops/connecting.html
代码如下:
package com.example.androidapp;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.util.Log;



/**
 * @author tuomas
 * This class provides basic helper functions and features for network communication.
 */


public class NetworkHelper 
{
private Context mContext;


public NetworkHelper(Context mContext)
{
    //get context
    this.mContext = mContext;
}


/**
 * Checks if the network connection is available.
 */
public boolean checkConnection()
{
    //checks if the network connection exists and works as should be
    ConnectivityManager connMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();

    if (networkInfo != null && networkInfo.isConnected())
    {
        //network connection works
        Log.v("log", "Network connection works");
        return true;
    }
    else
    {
        //network connection won't work
        Log.v("log", "Network connection won't work");
        return false;
    }

}

public void downloadUrl(String stringUrl)
{
    new DownloadWebpageTask().execute(stringUrl);

}



//actual code to handle download
private class DownloadWebpageTask extends AsyncTask<String, Void, String>
{



    @Override
    protected String doInBackground(String... urls)
    {
        // params comes from the execute() call: params[0] is the url.
        try {
            return downloadUrl(urls[0]);
        } catch (IOException e) {
            return "Unable to retrieve web page. URL may be invalid.";
        }
    }

    // Given a URL, establishes an HttpUrlConnection and retrieves
    // the web page content as a InputStream, which it returns as
    // a string.
    private String downloadUrl(String myurl) throws IOException 
    {
        InputStream is = null;
        // Only display the first 500 characters of the retrieved
        // web page content.
        int len = 500;

        try {
            URL url = new URL(myurl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(10000 );
            conn.setConnectTimeout(15000);
            conn.setRequestMethod("GET");
            conn.setDoInput(true);
            // Starts the query
            conn.connect();
            int response = conn.getResponseCode();
            Log.d("log", "The response is: " + response);
            is = conn.getInputStream();

            // Convert the InputStream into a string
            String contentAsString = readIt(is, len);
            return contentAsString;

        // Makes sure that the InputStream is closed after the app is
        // finished using it.
        } finally {
            if (is != null) {
                is.close();
            } 
        }
    }

    // Reads an InputStream and converts it to a String.
    public String readIt(InputStream stream, int len) throws IOException, UnsupportedEncodingException 
    {
        Reader reader = null;
        reader = new InputStreamReader(stream, "UTF-8");        
        char[] buffer = new char[len];
        reader.read(buffer);
        return new String(buffer);
    }


    // onPostExecute displays the results of the AsyncTask.
    @Override
    protected void onPostExecute(String result) 
    {
        //textView.setText(result);
        Log.v("log", result);

    }

} 

在我的活动类中,我这样使用该类:

connHelper = new NetworkHelper(this);

...

if (connHelper.checkConnection())
    {
        //connection ok, download the webpage from provided url
        connHelper.downloadUrl(stringUrl);
    }

我遇到的问题是,我应该以某种方式回调到活动中,并且在“downloadUrl()”函数中应该定义它。例如,当下载完成时,在活动中调用public void “handleWebpage(String data)”函数,并将加载的字符串作为其参数。
我搜索了一些资料并发现,我应该以某种方式使用接口来实现这个功能。 在查看了几个类似的stackoverflow问题/答案后,我没有成功地实现它,并且不确定我是否正确地理解了接口:如何在Java中将方法作为参数传递? 老实说,对我来说,使用匿名类是新的,我不太确定应该在哪里或者如何应用上述线程中的示例代码片段。
所以我的问题是,我该如何将回调函数传递给我的网络类,并在下载完成后调用它? 接口声明在哪里,实现关键字等等?
请注意,我是Java的初学者(尽管有其他编程背景),因此我会非常感激详细的解释 :) 谢谢!
7个回答

119

使用回调接口或抽象类与抽象回调方法。

回调接口示例:

public class SampleActivity extends Activity {

    //define callback interface
    interface MyCallbackInterface {

        void onDownloadFinished(String result);
    }

    //your method slightly modified to take callback into account 
    public void downloadUrl(String stringUrl, MyCallbackInterface callback) {
        new DownloadWebpageTask(callback).execute(stringUrl);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //example to modified downloadUrl method
        downloadUrl("http://google.com", new MyCallbackInterface() {

            @Override
            public void onDownloadFinished(String result) {
                // Do something when download finished
            }
        });
    }

    //your async task class
    private class DownloadWebpageTask extends AsyncTask<String, Void, String> {

        final MyCallbackInterface callback;

        DownloadWebpageTask(MyCallbackInterface callback) {
            this.callback = callback;
        }

        @Override
        protected void onPostExecute(String result) {
            callback.onDownloadFinished(result);
        }

        //except for this leave your code for this class untouched...
    }
}
第二个选项更为简洁。您甚至不需要为“onDownloaded事件”定义抽象方法,因为onPostExecute正好满足所需。只需在您的downloadUrl方法中使用匿名内部类扩展您的DownloadWebpageTask即可。
    //your method slightly modified to take callback into account 
    public void downloadUrl(String stringUrl, final MyCallbackInterface callback) {
        new DownloadWebpageTask() {

            @Override
            protected void onPostExecute(String result) {
                super.onPostExecute(result);
                callback.onDownloadFinished(result);
            }
        }.execute(stringUrl);
    }

    //...

5
谢谢,这帮助我解决了问题,我现在认为我已经理解了接口的基本概念 :) - Tumetsu
3
有趣的是,接口在一般的Java编程中扮演着重要角色。 - Anderson Madeira

47

不需要接口、库或Java 8!

只需使用java.util.concurrent中的Callable<V>即可。

public static void superMethod(String simpleParam, Callable<Void> methodParam) {

    //your logic code [...]

    //call methodParam
    try {
        methodParam.call();

    } catch (Exception e) {
        e.printStackTrace();
    }
}

如何使用它:

 superMethod("Hello world", new Callable<Void>() {
                public Void call() {
                    myParamMethod();
                    return null;
                }
            }
    );

其中myParamMethod()是我们传递的参数方法(在这种情况下为methodParam)。


谢谢您的回答。然而,这个例子并没有很清楚地说明(我整晚都在熬夜,所以请原谅我的愚蠢问题)myParamMethod如何传递simpleParam。例如,我有一个包装器围绕Ion,我将服务器参数和目标URL封装在Json中传递给它,我是使用superMethod(serverParams, callEndpointIon)还是每次都要覆盖Callable? - kilokahn
1
如果您正在使用 Callable<Void>,那么没有什么理由不使用 Runnable,因为您无论如何都会返回 Void。这将消除对 return null; 语句的需求。 - Nathan F.
2
(不带任何输入参数) - Nolesh
1
如何将参数传递给可调用的方法,例如 methodParam.call(object); 并接收 public Void call(JSONObject object) 方法中的参数: // myParamMethod(JSONObject object); return null; - Jhon Jesus
这太完美了! - Darwin Marcelo

28

是的,在我看来,接口是最好的方式。例如,GWT使用命令模式和像这样的接口:

public interface Command{
    void execute();
}

这样,您可以从一个方法传递函数到另一个方法中。

public void foo(Command cmd){
  ...
  cmd.execute();
}

public void bar(){
  foo(new Command(){
     void execute(){
        //do something
     }
  });
}

6
GWT是什么,如何传递任何参数? - Buksy
1
@Buksy 这是你要找的吗?公共接口命令: public interface Command{ void execute(Object... object); }可传递无限对象 :D - M at
你可以使用java.lang.Runnable,这基本上是相同的方法,但你不必定义一个接口。 - BamsBamx

11

这个问题在Java中无法直接解决。 Java不支持 Higher-order functions,但可以通过一些“技巧”实现。通常使用的是接口,就像你看到的那样。请参考这里获取更多信息。您还可以使用反射来实现,但这种方法容易出错。


1
这并不是一个很好的答案,因为你所做的只是建议去查看,更适合作为评论。 - Chris Stratton
11
由于他的声望不足50,他无法发表评论,只能回答问题。我一直不喜欢这一点。 - Display Name is missing
1
非常有用的概念性答案,适合有经验的程序员转向Java。谢谢@olorin! - Fattie

7

在Java编程架构中,使用接口可能是最好的方法。

但是,传递一个Runnable对象也可以起作用,而且我认为这将更加实用和灵活。

 SomeProcess sp;

 public void initSomeProcess(Runnable callbackProcessOnFailed) {
     final Runnable runOnFailed = callbackProcessOnFailed; 
     sp = new SomeProcess();
     sp.settingSomeVars = someVars;
     sp.setProcessListener = new SomeProcessListener() {
          public void OnDone() {
             Log.d(TAG,"done");
          }
          public void OnFailed(){
             Log.d(TAG,"failed");
             //call callback if it is set
             if (runOnFailed!=null) {
               Handler h = new Handler();
               h.post(runOnFailed);
             }
          }               
     };
}

/****/

initSomeProcess(new Runnable() {
   @Override
   public void run() {
       /* callback routines here */
   }
});

1
非常干净整洁的实现。 - Desolator

0

反射从来不是一个好主意,因为它更难以阅读和调试,但如果你百分之百确定自己在做什么,你可以简单地调用类似 set_method(R.id.button_profile_edit, "toggle_edit") 的方法将一个方法附加到视图上。这在片段中非常有用,但同样,一些人会认为它是反模式,所以请注意。

public void set_method(int id, final String a_method)
{
    set_listener(id, new View.OnClickListener() {
        public void onClick(View v) {
            try {
                Method method = fragment.getClass().getMethod(a_method, null);
                method.invoke(fragment, null);
            } catch (Exception e) {
                Debug.log_exception(e, "METHOD");
            }
        }
    });
}
public void set_listener(int id, View.OnClickListener listener)
{
    if (root == null) {
        Debug.log("WARNING fragment", "root is null - listener not set");
        return;
    }
    View view = root.findViewById(id);
    view.setOnClickListener(listener);
}

0

接口回调支持通用类型。

在Callbackable.java中。

public interface Callbackable<T> {
    void call(T obj);
}

使用方法:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        doSomeNetworkAction(new Callbackable<Integer>() {
            @Override
            public void call(Integer obj) {
                System.out.println("have received: " + obj + " from network");
            }
        });

    }
    
    // You can change Integer to String or to any model class like Customer, Profile, Address... 
    public void doSomeNetworkAction(Callbackable<Integer> callback) {
        // acb xyz...
        callback.call(666);
    }
}

我认为在Java中不能直接实例化一个接口? - chitgoks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接