Java传递方法作为参数

我正在寻找一种通过引用传递方法的方法。我知道Java不传递方法作为参数,但是,我想要一个替代方案。

我被告知接口是作为参数传递方法的替代方案,但我不理解接口如何通过引用充当方法。如果我理解正确的话,接口只是一组没有定义的抽象方法。我不想发送一个每次都需要定义的接口,因为几个不同的方法可以用相同的参数调用相同的方法。

我想要完成的是类似这样的事情:

public void setAllComponents(Component[] myComponentArray, Method myMethod) {
for (Component leaf : myComponentArray) {
if (leaf instanceof Container) { //recursive call if Container
Container node = (Container) leaf;
setAllComponents(node.getComponents(), myMethod);
} //end if node
myMethod(leaf);
} //end looping through components
}

调用方法如下:

setAllComponents(this.getComponents(), changeColor());
setAllComponents(this.getComponents(), changeSize());
494494 次浏览

使用java.lang.reflect.Method对象并调用invoke

编辑:在Java 8中,lambda表达式是一个很好的解决方案,正如其他 答案所指出的那样。下面的答案是为Java 7和更早的版本编写的…


看一下命令模式

// NOTE: code not tested, but I believe this is valid java...
public class CommandExample
{
public interface Command
{
public void execute(Object data);
}


public class PrintCommand implements Command
{
public void execute(Object data)
{
System.out.println(data.toString());
}
}


public static void callCommand(Command command, Object data)
{
command.execute(data);
}


public static void main(String... args)
{
callCommand(new PrintCommand(), "hello world");
}
}

编辑:作为Pete Kirkham指出,还有另一种方法使用游客。访问者方法稍微复杂一些——你的节点都需要通过acceptVisitor()方法来感知访问者——但如果你需要遍历一个更复杂的对象图,那么它就值得研究。

使用观察者模式(有时也称为监听者模式):

interface ComponentDelegate {
void doSomething(Component component);
}


public void setAllComponents(Component[] myComponentArray, ComponentDelegate delegate) {
// ...
delegate.doSomething(leaf);
}


setAllComponents(this.getComponents(), new ComponentDelegate() {
void doSomething(Component component) {
changeColor(component); // or do directly what you want
}
});

new ComponentDelegate()...声明了实现接口的匿名类型。

首先用要作为参数传递的方法定义一个Interface

public interface Callable {
public void call(int param);
}

使用该方法实现一个类

class Test implements Callable {
public void call(int param) {
System.out.println( param );
}
}

//像这样调用

Callable cmd = new Test();

这允许您将cmd作为参数传递,并调用接口中定义的方法调用

public invoke( Callable callable ) {
callable.call( 5 );
}

上次我检查的时候,Java不能在本地做你想做的事情;你必须使用“变通方法”来绕过这些限制。在我看来,接口是一种选择,但不是一个好的选择。也许告诉你这句话的人的意思是这样的:

public interface ComponentMethod {
public abstract void PerfromMethod(Container c);
}


public class ChangeColor implements ComponentMethod {
@Override
public void PerfromMethod(Container c) {
// do color change stuff
}
}


public class ChangeSize implements ComponentMethod {
@Override
public void PerfromMethod(Container c) {
// do color change stuff
}
}


public void setAllComponents(Component[] myComponentArray, ComponentMethod myMethod) {
for (Component leaf : myComponentArray) {
if (leaf instanceof Container) { //recursive call if Container
Container node = (Container) leaf;
setAllComponents(node.getComponents(), myMethod);
} //end if node
myMethod.PerfromMethod(leaf);
} //end looping through components
}

然后你可以调用:

setAllComponents(this.getComponents(), new ChangeColor());
setAllComponents(this.getComponents(), new ChangeSize());
Java确实有一种机制来传递名称和调用它。这是反射机制的一部分。 你的函数需要额外的Method类参数
public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}

虽然这对Java 7及以下版本还不有效,但我相信我们应该展望未来,至少认识到这些变化将出现在Java 8等新版本中。

也就是说,这个新版本为Java带来了λ和方法引用(以及新的api,这是这个问题的另一个有效解决方案。虽然它们仍然需要一个接口,但不会创建新的对象,并且额外的类文件不会因为JVM的不同处理而污染输出目录。

这两种风格(lambda和方法引用)都需要一个具有单一方法的可用接口,该方法的签名被使用:

public interface NewVersionTest{
String returnAString(Object oIn, String str);
}

从这里开始,方法的名称不再重要。在接受lambda的地方,也接受方法引用。例如,在这里使用我们的签名:

public static void printOutput(NewVersionTest t, Object o, String s){
System.out.println(t.returnAString(o, s));
}

这只是一个简单的接口调用,直到传递lambda1:

public static void main(String[] args){
printOutput( (Object oIn, String sIn) -> {
System.out.println("Lambda reached!");
return "lambda return";
}
);
}

这将输出:

Lambda reached!
lambda return

方法引用类似。考虑到:

public class HelperClass{
public static String testOtherSig(Object o, String s){
return "real static method";
}
}

和主要:

public static void main(String[] args){
printOutput(HelperClass::testOtherSig);
}

输出将是real static method方法引用可以是静态的、实例的、带有任意实例的非静态的,甚至是构造函数。对于构造函数,将使用类似ClassName::new的东西。

1有些人不认为这是一个lambda,因为它有副作用。不过,它确实以一种更直观的方式说明了如何使用。

如果你不需要这些方法来返回一些东西,你可以让它们返回Runnable对象。

private Runnable methodName (final int arg) {
return (new Runnable() {
public void run() {
// do stuff with arg
}
});
}

然后这样使用它:

private void otherMethodName (Runnable arg){
arg.run();
}

在Java 8中,现在可以使用Lambda表达式和方法引用更容易地传递方法。首先介绍一些背景知识:函数式接口是具有且仅有一个抽象方法的接口,尽管它可以包含任意数量的默认的方法 (Java 8新增)和静态方法。lambda表达式可以快速实现抽象方法,如果不使用lambda表达式,则无需使用所有不必要的语法。

没有lambda表达式:

obj.aMethod(new AFunctionalInterface() {
@Override
public boolean anotherMethod(int i)
{
return i == 982
}
});

使用lambda表达式:

obj.aMethod(i -> i == 982);

下面是Java Lambda表达式教程的节选:

Lambda表达式的语法

lambda表达式由以下内容组成:

  • 括号内以逗号分隔的形式参数列表。CheckPerson。测试方法包含一个参数p, 它表示Person类的实例。< br > < br > 请注意:你 可以省略lambda表达式中参数的数据类型。在 另外,如果只有一个参数,可以省略括号。 例如,下面的lambda表达式也是有效的:

    p -> p.getGender() == Person.Sex.MALE
    && p.getAge() >= 18
    && p.getAge() <= 25
    
  • The arrow token, ->

  • A body, which consists of a single expression or a statement block. This example uses the following expression:

    p.getGender() == Person.Sex.MALE
    && p.getAge() >= 18
    && p.getAge() <= 25
    

    如果指定单个表达式,则Java运行时计算表达式,然后返回其值。另外, 你可以使用return语句:

    p -> {
    return p.getGender() == Person.Sex.MALE
    && p.getAge() >= 18
    && p.getAge() <= 25;
    }
    

    return语句不是表达式;在lambda表达式中,必须将语句括在大括号({})中。然而,你没有 将void方法调用括在大括号中。例如, 下面是一个有效的lambda表达式:

    email -> System.out.println(email)
    

Note that a lambda expression looks a lot like a method declaration; you can consider lambda expressions as anonymous methods—methods without a name.


Here is how you can "pass a method" using a lambda expression:

interface I {
public void myMethod(Component component);
}


class A {
public void changeColor(Component component) {
// code here
}


public void changeSize(Component component) {
// code here
}
}
class B {
public void setAllComponents(Component[] myComponentArray, I myMethodsInterface) {
for(Component leaf : myComponentArray) {
if(leaf instanceof Container) { // recursive call if Container
Container node = (Container)leaf;
setAllComponents(node.getComponents(), myMethodInterface);
} // end if node
myMethodsInterface.myMethod(leaf);
} // end looping through components
}
}
class C {
A a = new A();
B b = new B();


public C() {
b.setAllComponents(this.getComponents(), component -> a.changeColor(component));
b.setAllComponents(this.getComponents(), component -> a.changeSize(component));
}
}

C可以通过使用类似这样的方法引用来进一步缩短:

class C {
A a = new A();
B b = new B();


public C() {
b.setAllComponents(this.getComponents(), a::changeColor);
b.setAllComponents(this.getComponents(), a::changeSize);
}
}

下面是一个基本的例子:

public class TestMethodPassing
{
private static void println()
{
System.out.println("Do println");
}


private static void print()
{
System.out.print("Do print");
}


private static void performTask(BasicFunctionalInterface functionalInterface)
{
functionalInterface.performTask();
}


@FunctionalInterface
interface BasicFunctionalInterface
{
void performTask();
}


public static void main(String[] arguments)
{
performTask(TestMethodPassing::println);
performTask(TestMethodPassing::print);
}
}

输出:

Do println
Do print

我不是java专家,但我可以这样解决你的问题:

@FunctionalInterface
public interface AutoCompleteCallable<T> {
String call(T model) throws Exception;
}

我在我的特殊接口中定义了参数

public <T> void initialize(List<T> entries, AutoCompleteCallable getSearchText) {.......
//call here
String value = getSearchText.call(item);
...
}

最后,我在调用初始化方法时实现了getSearchText方法。

initialize(getMessageContactModelList(), new AutoCompleteCallable() {
@Override
public String call(Object model) throws Exception {
return "custom string" + ((xxxModel)model.getTitle());
}
})

自Java 8以来,有一个Function<T, R>接口(文档),它有一个方法

R apply(T t);

您可以使用它将函数作为参数传递给其他函数。T是函数的输入类型,R是返回类型。

在你的例子中,你需要传递一个函数,它接受Component类型作为输入,并且不返回任何东西——Void。在这种情况下,Function<T, R>不是最好的选择,因为没有Void类型的自动装箱。你要找的接口叫做Consumer<T> (文档) with method

void accept(T t);

它看起来是这样的:

public void setAllComponents(Component[] myComponentArray, Consumer<Component> myMethod) {
for (Component leaf : myComponentArray) {
if (leaf instanceof Container) {
Container node = (Container) leaf;
setAllComponents(node.getComponents(), myMethod);
}
myMethod.accept(leaf);
}
}

你可以使用方法引用来调用它:

setAllComponents(this.getComponents(), this::changeColor);
setAllComponents(this.getComponents(), this::changeSize);

假设在同一个类中定义了changeColor()和changeSize()方法。


如果你的方法碰巧接受多个参数,你可以使用BiFunction<T, U, R> - T和U作为输入参数的类型,R作为返回类型。还有BiConsumer<T, U>(两个参数,没有返回类型)。不幸的是,对于3个或更多的输入参数,您必须自己创建一个接口。例如:

public interface Function4<A, B, C, D, R> {


R apply(A a, B b, C c, D d);
}

关于如何将java.util.function.Function用于简单方法作为参数函数,我没有找到任何足够明确的示例。这里有一个简单的例子:

import java.util.function.Function;


public class Foo {


private Foo(String parameter) {
System.out.println("I'm a Foo " + parameter);
}


public static Foo method(final String parameter) {
return new Foo(parameter);
}


private static Function parametrisedMethod(Function<String, Foo> function) {
return function;
}


public static void main(String[] args) {
parametrisedMethod(Foo::method).apply("from a method");
}
}

基本上你有一个带有默认构造函数的Foo对象。一个method,它将作为参数从类型为Function<String, Foo>parametrisedMethod中调用。

  • Function<String, Foo>表示该函数以String作为参数并返回Foo
  • Foo::Method对应于类似x -> Foo.method(x);的lambda
  • parametrisedMethod(Foo::method)可以看作是x -> parametrisedMethod(Foo.method(x))
  • .apply("from a method")基本上是做parametrisedMethod(Foo.method("from a method"))

然后在输出中返回:

>> I'm a Foo from a method

示例应该按原样运行,然后您可以使用不同的类和接口从上面的答案中尝试更复杂的东西。

我没有找到任何解决方案,在这里,显示如何传递方法与参数绑定到它作为一个方法的参数。下面的例子演示了如何传递一个已经绑定了参数值的方法。

  1. 步骤1:创建两个接口,一个有返回类型,另一个没有。Java也有类似的接口,但它们没有什么实际用途,因为它们不支持异常抛出。



public interface Do {
void run() throws Exception;
}




public interface Return {
R run() throws Exception;
}


  1. 我们如何使用两个接口在事务中封装方法调用的示例。注意,我们传递方法时带有实际参数。



//example - when passed method does not return any value
public void tx(final Do func) throws Exception {
connectionScope.beginTransaction();
try {
func.run();
connectionScope.commit();
} catch (Exception e) {
connectionScope.rollback();
throw e;
} finally {
connectionScope.close();
}
}


//Invoke code above by
tx(() -> api.delete(6));


另一个例子展示了如何传递一个实际返回值的方法




public  R tx(final Return func) throws Exception {
R r=null;
connectionScope.beginTransaction();
try {
r=func.run();
connectionScope.commit();
} catch (Exception e) {
connectionScope.rollback();
throw e;
} finally {
connectionScope.close();
}
return r;
}
//Invoke code above by
Object x= tx(() -> api.get(id));


带有反射的解的例子,通过的方法必须是公共的

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;


public class Program {
int i;


public static void main(String[] args) {
Program   obj = new Program();    //some object


try {
Method method = obj.getClass().getMethod("target");
repeatMethod( 5, obj, method );
}
catch ( NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
System.out.println( e );
}
}


static void repeatMethod (int times, Object object, Method method)
throws IllegalAccessException, InvocationTargetException {


for (int i=0; i<times; i++)
method.invoke(object);
}
public void target() {                 //public is necessary
System.out.println("target(): "+ ++i);
}
}

我很欣赏上面的答案,但我可以使用下面的方法实现相同的行为;这个想法借鉴自Javascript的回调。我对修正持开放态度,尽管到目前为止(在生产中)还不错。

其思想是在签名中使用函数的返回类型,这意味着yield必须是静态的。

下面是一个运行带有超时的进程的函数。

public static void timeoutFunction(String fnReturnVal) {


Object p = null; // whatever object you need here


String threadSleeptime = null;


Config config;


try {
config = ConfigReader.getConfigProperties();
threadSleeptime = config.getThreadSleepTime();


} catch (Exception e) {
log.error(e);
log.error("");
log.error("Defaulting thread sleep time to 105000 miliseconds.");
log.error("");
threadSleeptime = "100000";
}


ExecutorService executor = Executors.newCachedThreadPool();
Callable<Object> task = new Callable<Object>() {
public Object call() {
// Do job here using --- fnReturnVal --- and return appropriate value
return null;
}
};
Future<Object> future = executor.submit(task);


try {
p = future.get(Integer.parseInt(threadSleeptime), TimeUnit.MILLISECONDS);
} catch (Exception e) {
log.error(e + ". The function timed out after [" + threadSleeptime
+ "] miliseconds before a response was received.");
} finally {
// if task has started then don't stop it
future.cancel(false);
}
}


private static String returnString() {
return "hello";
}


public static void main(String[] args) {
timeoutFunction(returnString());
}

java 8起

Java 8以后,您可以使用lambda表达式提供函数接口(只有一个抽象方法的接口)的抽象方法的实现,并将其作为参数传递给方法。

@FunctionalInterface
interface ArithmeticFunction {
public int calcualate(int a, int b);
}


public class Main {
public static void main(String args[]) {
ArithmeticFunction addition = (a, b) -> a + b;
ArithmeticFunction subtraction = (a, b) -> a - b;


int a = 20, b = 5;


System.out.println(perform(addition, a, b));
// or
System.out.println(perform((x, y) -> x + y, a, b));


System.out.println(perform(subtraction, a, b));
// or
System.out.println(perform((x, y) -> x - y, a, b));
}


static int perform(ArithmeticFunction function, int a, int b) {
return function.calcualate(a, b);
}
}

输出:

25
25
15
15

ONLINE DEMO

方法参考了解更多关于它的信息。