如何在Java中传递和调用方法引用

6
假设我有一个名为Server的类,我想让其他人为它编写插件。假设Plugin是一个接口,它扩展了Runnable并添加了一个方法:void init(...)。插件的工作是收集数据并将其发送到服务器。但是,当发送数据到服务器的时间到来时,它该如何做呢?从C和C++转过来,我希望能够思考一下函数指针的思路。虽然在Java中似乎也有可能实现,但我没有在Java标准类库之外找到过例子。
如何传递方法引用给init方法,以便它可以被Plugin存储,然后在Plugin想要发送数据时如何调用该方法?暂时假设所需的Server方法是:void sendData(Integer data)
例如:
// Inside Server
Plugin p = new PluginImplementation();
p.init(this::sendData);    

// Plugin init
public void init(?? sendMethod) {
    storedSendMethod = sendMethod;
    // ...
}

// Plugin run
public void run() {
    // ...
    storedSendMethod(x) // Sends data to server
    // ...
}
5个回答

5

使用java.util.function.Function,我们可以将一个函数作为参数传递给一个方法,并使用apply()将其应用到相应的参数上。下面是一个例子:

import java.util.function.Function;

public class FunctionDemo {

    // we will pass a reference to this method
    public static Integer square(Integer x) {
        return x * x;
    }

    // this method accepts the function as an argument and applies it to the input: 5
    public static Integer doSomething(Function<Integer, Integer> func) {
        return func.apply(5);
    }

    public static void main(String[] args) {
        // and here's how to use it
        System.out.println(doSomething(FunctionDemo::square)); // prints 25
    }   
}

多个参数的附加版本(作为数组传递):

public static Integer sum(Integer[] x) {
    Integer result = 0;
    for(int i = 0; i < x.length; i++)
        result += x[i];
    return result;
}

public static void main(String[] args) {
    Integer[] arr = {1,2,3,4,5};
    System.out.println(doSomething(Play::sum, arr));
}

public static Integer doSomething(Function<Integer[], Integer> func,
                                  Integer[] arr) {        
    return func.apply(arr);
}

这个方法能够扩展到有多个参数的方法吗?(不使用包装类)。我快速查看了文档,似乎只允许两种模板类型。如果必须使用包装类,我也无所谓。 - Ephemera
1
@Ephemera 当然,看看我刚刚添加的例子。 - Nir Alfasi
1
@Ephemera:如果你在谈论内置函数类型,那么只有预定义的带有最多两个参数的interface,但是创建自己的函数式接口并没有问题。 - Holger

2
如果方法是void sendData(Integer data),它对应于一个消费者,该消费者接受一个整数并返回一个void,该接口由内置的Consumer<Integer>接口来覆盖,该接口具有一个accept(Integer)方法,当调用时将调用您的函数。
因此,您的代码将如下所示:
public void init(Consumer<Integer> sendMethod) {
    storedSendMethod = sendMethod;
    // ...
}

// Plugin run
 void run() {
    // ...
    storedSendMethod.accept(x) // Sends data to server
   // ...
}

作为一种附注,拥有一个init方法可能是一种不好的Java设计。如果可能的话,最好将初始化移动到构造函数中。
Plugin p = new PluginImplementation( this::sendData);

这个能扩展来接受多个参数吗(不用创建一个包装类)?我确实考虑过 init,但目前为了遵循我正在使用的另一个设计,我将其放在那里。 - Ephemera
2
Consumer#accept() 只接受一个参数。但是还有一个 BiConsumer 接受两个参数。如果你需要更多的参数,你可以自己创建一个接口或使用已经存在的接口。 - dkatzel

1

在Java中,您可以使用回调来实现这一点,这是您的回调接口:

public interface SendCallback {
    public void doSend(Object toSend);
}

这是插件接口,所有插件都必须实现此接口。

public interface Plugin extends Runnable {
    public void init(SendCallback callback);
}

这是服务器的代码。
public class Server {

    Plugin plugin;

    SendCallback callback = new SendCallback() {
        public void doSend(Object toSend) {
                // logic to send object 'toSend'
        }
    }

    public Server() {
        plugin = new MyPlugin();
        plugin.init(callback);
    }

}

这是您的插件实现。
public class MyPlugin implements Plugin {
    SendCallback callback = null;
    Object x = null;

    public void init(SendCallback callback) {
        this.callback = callback;
    }
    public void run() {
        x = "Somthing"; // initialize the x object
        callback.doSend(x);
    }
} 

你会注意到,服务器定义了回调实现。插件将调用回调的方法doSend。
我希望这有所帮助。

0

Java 8 中有方法引用,但是你也可以直接传递整个对象并调用其 sendData() 方法。在“插件”情况下,为每个使用接口有助于插件和服务器之间具有“松散”的耦合。

public interface Server {
    void setData(...);
}

public class MyPlugin implements plugin {

   private Server server;   

   void init(Server s )  {
       this.server = s;
   }

   void run() {
      ...
      this.server.setData(...);
      ...
   }

}

0
interface Server{
  ...
  void sendData(String message);
}

插件不需要函数引用,您可以使用服务器接口来通知插件了解该方法。

class PluginX implements Plugin{
    ...
    private Server server;
    void init(Server server) {
        this.server = server;
    }

    public void run() {
    // ...
        server.sendData(x) // Sends data to server
    // ...

} }


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