我有一个方法,大约有十行代码。我想创建更多的方法来做完全相同的事情,除了一个会改变一行代码的小计算。这是一个传递函数指针来替换这一行的完美应用程序,但是Java没有函数指针。我最好的选择是什么?


当前回答

在Java 8之前,类似函数指针的功能最接近的替代品是匿名类。例如:

Collections.sort(list, new Comparator<CustomClass>(){
    public int compare(CustomClass a, CustomClass b)
    {
        // Logic to compare objects of class CustomClass which returns int as per contract.
    }
});

但现在在Java 8中,我们有一个非常简洁的替代方法,称为lambda表达式,它可以用作:

list.sort((a, b) ->  { a.isBiggerThan(b) } );

其中isBiggerThan是CustomClass中的一个方法。我们也可以在这里使用方法引用:

list.sort(MyClass::isBiggerThan);

其他回答

匿名内部类

假设你想要传入一个带有String参数的函数,该参数返回一个整型。 首先,如果不能重用现有的接口,则必须定义一个接口,该接口的唯一成员是该函数。

interface StringFunction {
    int func(String param);
}

接受指针的方法只接受StringFunction实例,如下所示:

public void takingMethod(StringFunction sf) {
   int i = sf.func("my string");
   // do whatever ...
}

并且会被这样称呼:

ref.takingMethod(new StringFunction() {
    public int func(String param) {
        // body
    }
});

编辑:在Java 8中,你可以用lambda表达式调用它:

ref.takingMethod(param -> bodyExpression);

方法引用,使用::操作符

当方法接受函数接口时,可以在方法参数中使用方法引用。函数接口是任何只包含一个抽象方法的接口。(一个功能接口可以包含一个或多个默认方法或静态方法。)

IntBinaryOperator是一个功能接口。它的抽象方法applyAsInt接受两个int作为参数,并返回一个int。数学。Max也接受两个int型并返回一个int型。在这个例子中,A.method(Math::max);使参数。applyAsInt将它的两个输入值发送给Math。返回Math.max的结果。

import java.util.function.IntBinaryOperator;

class A {
    static void method(IntBinaryOperator parameter) {
        int i = parameter.applyAsInt(7315, 89163);
        System.out.println(i);
    }
}
import java.lang.Math;

class B {
    public static void main(String[] args) {
        A.method(Math::max);
    }
}

一般来说,你可以使用:

method1(Class1::method2);

而不是:

method1((arg1, arg2) -> Class1.method2(arg1, arg2));

它是:

method1(new Interface1() {
    int method1(int arg1, int arg2) {
        return Class1.method2(arg1, agr2);
    }
});

有关更多信息,请参阅Java 8和Java语言规范§15.13中的::(双冒号)操作符。

从Java8开始,您可以使用lambdas,它在官方SE 8 API中也有库。

用法: 您只需要使用带有一个抽象方法的接口。 创建一个实例(你可能想使用java SE 8已经提供的实例),如下所示:

Function<InputType, OutputType> functionname = (inputvariablename) {
... 
return outputinstance;
}

有关更多信息,请查看文档:https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

你也可以这样做(在一些罕见的情况下是有意义的)。问题(这是一个大问题)是您失去了使用类/接口的所有类型安全性,并且您必须处理方法不存在的情况。

它确实有一个“好处”,即您可以忽略访问限制并调用私有方法(示例中没有显示,但您可以调用编译器通常不允许您调用的方法)。

同样,这在很少的情况下是有意义的,但在那些情况下,这是一个很好的工具。

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

class Main
{
    public static void main(final String[] argv)
        throws NoSuchMethodException,
               IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        final String methodName;
        final Method method;
        final Main   main;

        main = new Main();

        if(argv.length == 0)
        {
            methodName = "foo";
        }
        else
        {
            methodName = "bar";
        }

        method = Main.class.getDeclaredMethod(methodName, int.class);

        main.car(method, 42);
    }

    private void foo(final int x)
    {
        System.out.println("foo: " + x);
    }

    private void bar(final int x)
    {
        System.out.println("bar: " + x);
    }

    private void car(final Method method,
                     final int    val)
        throws IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        method.invoke(this, val);
    }
}

开源安全镜像项目将上面提到的一些解决方案泛化到一个向Java添加函数、委托和事件的库中。

有关特性的小抄,请参阅README或此stackoverflow答案。

至于函数,标准库引入了一个Fun接口和一些子接口(与泛型一起),它们组成了一个流畅的API,用于将方法用作类型。

Fun.With0Params<String> myFunctionField = "   hello world   "::trim;`  
Fun.With2Params<Boolean, Object, Object> equals = Objects::equals;`  
    
public void foo(Fun.With1ParamAndVoid<String> printer) throws Exception {
    printer.invoke("hello world);
}  

public void test(){
    foo(System.out::println);
}  

注意:

必须选择与目标签名中的参数数量相匹配的子接口。Fx,如果它有一个参数,选择Fun.With1Param。 泛型用于定义A)返回类型和B)签名的参数。

另外,请注意传递给foo()方法调用的方法引用的签名必须与方法foo定义的Fun匹配。如果不这样做,编译器将发出一个错误。