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


当前回答

在用Java编程时,我真正怀念的一件事是函数回调。在递归处理层次结构中,需要不断地呈现这些元素,您希望为每个元素执行特定的操作。比如遍历目录树,或者处理数据结构。我内心的极简主义者讨厌为每个特定的情况定义接口和实现。

有一天我发现自己在想为什么不呢?我们有方法指针——method对象。通过优化JIT编译器,反射调用实际上不再带来巨大的性能损失。此外,除了将文件从一个位置复制到另一个位置之外,反射方法调用的成本变得微不足道。

随着我对它的深入思考,我意识到OOP范式中的回调需要将对象和方法绑定在一起——进入回调对象。

请查看我的基于反射的Java回调解决方案。免费供任何用途。

其他回答

在没有函数数组接口的情况下做同样的事情:

class NameFuncPair
{
    public String name;                // name each func
    void   f(String x) {}              // stub gets overridden
    public NameFuncPair(String myName) { this.name = myName; }
}

public class ArrayOfFunctions
{
    public static void main(String[] args)
    {
        final A a = new A();
        final B b = new B();

        NameFuncPair[] fArray = new NameFuncPair[]
        {
            new NameFuncPair("A") { @Override void f(String x) { a.g(x); } },
            new NameFuncPair("B") { @Override void f(String x) { b.h(x); } },
        };

        // Go through the whole func list and run the func named "B"
        for (NameFuncPair fInstance : fArray)
        {
            if (fInstance.name.equals("B"))
            {
                fInstance.f(fInstance.name + "(some args)");
            }
        }
    }
}

class A { void g(String args) { System.out.println(args); } }
class B { void h(String args) { System.out.println(args); } }

开源安全镜像项目将上面提到的一些解决方案泛化到一个向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匹配。如果不这样做,编译器将发出一个错误。

@sblundy的回答很好,但匿名内部类有两个小缺陷,主要是它们往往不可重用,其次是庞大的语法。

好处是他的模式扩展到完整的类,而不需要对主类(执行计算的类)进行任何更改。

当你实例化一个新类时,你可以将参数传递到这个类中,这些参数可以作为等式中的常量——所以如果你的一个内部类看起来像这样:

f(x,y)=x*y

但有时你需要的是:

f(x,y)=x*y*2

也许第三个原因是:

f(x,y)=x*y/2

而不是创建两个匿名的内部类或添加一个“传递”参数,你可以创建一个实例化为:

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f=new InnerFunc(2.0);// for the second
calculateUsing(f);
f=new InnerFunc(0.5);// for the third
calculateUsing(f);

它将简单地将常量存储在类中,并在接口中指定的方法中使用它。

事实上,如果知道你的函数不会被存储/重用,你可以这样做:

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f.setConstant(2.0);
calculateUsing(f);
f.setConstant(0.5);
calculateUsing(f);

但是不可变的类更安全——我找不到一个理由让这样的类成为可变的。

我之所以发布这篇文章,是因为每当我听到匿名的内部类时,我就会感到畏缩——我看到过很多“必需”的冗余代码,因为程序员做的第一件事就是在他应该使用实际类的时候使用匿名,并且从来没有重新考虑过他的决定。

匿名内部类

假设你想要传入一个带有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);

新的Java 8函数接口和方法引用使用::操作符。

Java 8能够使用“@ Functional Interface”指针维护方法引用(MyClass::new)。不需要相同的方法名称,只需要相同的方法签名。

例子:

@FunctionalInterface
interface CallbackHandler{
    public void onClick();
}

public class MyClass{
    public void doClick1(){System.out.println("doClick1");;}
    public void doClick2(){System.out.println("doClick2");}
    public CallbackHandler mClickListener = this::doClick;

    public static void main(String[] args) {
        MyClass myObjectInstance = new MyClass();
        CallbackHandler pointer = myObjectInstance::doClick1;
        Runnable pointer2 = myObjectInstance::doClick2;
        pointer.onClick();
        pointer2.run();
    }
}

那么,我们得到了什么?

function Interface——这是一个接口,无论是否带有@FunctionalInterface注释,它只包含一个方法声明。 方法引用-这只是特殊的语法,看起来像这样,objectInstance::methodName,不多不少。 用法示例-只是一个赋值操作符,然后调用接口方法。

您应该仅为侦听器使用函数接口,而且仅用于侦听器!

因为所有其他类似的函数指针都不利于代码的可读性和理解能力。然而,直接方法引用有时很方便,例如foreach。

有几个预定义的功能接口:

Runnable              -> void run( );
Supplier<T>           -> T get( );
Consumer<T>           -> void accept(T);
Predicate<T>          -> boolean test(T);
UnaryOperator<T>      -> T apply(T);
BinaryOperator<T,U,R> -> R apply(T, U);
Function<T,R>         -> R apply(T);
BiFunction<T,U,R>     -> R apply(T, U);
//... and some more of it ...
Callable<V>           -> V call() throws Exception;
Readable              -> int read(CharBuffer) throws IOException;
AutoCloseable         -> void close() throws Exception;
Iterable<T>           -> Iterator<T> iterator();
Comparable<T>         -> int compareTo(T);
Comparator<T>         -> int compare(T,T);

对于早期的Java版本,您应该尝试Guava库,正如Adrian Petrescu上面提到的,它具有类似的功能和语法。

如需进一步研究,请参阅Java 8 Cheatsheet

感谢戴帽子的家伙提供的Java语言规范§15.13链接。