在Java中,函数指针最接近的替代品是什么?

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

91494 次浏览

匿名内部类

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

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);
对于每个“函数指针”,我将创建一个小的函子课来实现你的计算。 定义一个所有类都将实现的接口,并将这些对象的实例传递到更大的函数中。这是"命令模式"和"策略模式"的组合

@sblundy的例子很好。

您需要创建一个接口,该接口提供您希望传递的函数。例如:

/**
* A simple interface to wrap up a function of one argument.
*
* @author rcreswick
*
*/
public interface Function1<S, T> {


/**
* Evaluates this function on it's arguments.
*
* @param a The first argument.
* @return The result.
*/
public S eval(T a);


}

然后,当你需要传递一个函数时,你可以实现这个接口:

List<Integer> result = CollectionUtilities.map(list,
new Function1<Integer, Integer>() {
@Override
public Integer eval(Integer a) {
return a * a;
}
});

最后,map函数使用Function1中传递的参数,如下所示:

   public static <K,R,S,T> Map<K, R> zipWith(Function2<R,S,T> fn,
Map<K, S> m1, Map<K, T> m2, Map<K, R> results){
Set<K> keySet = new HashSet<K>();
keySet.addAll(m1.keySet());
keySet.addAll(m2.keySet());


results.clear();


for (K key : keySet) {
results.put(key, fn.eval(m1.get(key), m2.get(key)));
}
return results;
}

如果您不需要传递参数,您通常可以使用Runnable而不是自己的接口,或者您可以使用各种其他技术使参数计数不那么“固定”,但这通常是与类型安全的权衡。(或者你可以重写你的函数对象的构造函数,以这种方式传递参数。有很多方法,其中一些在特定情况下效果更好。)

我觉得这是个策略模式。查看fluffycat.com Java模式。

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

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

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

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);
}
}

如果你只有一个不同的行,你可以添加一个参数,比如一个标志和一个If (flag)语句,它调用一行或另一行。

当在一行中可以执行预定义数量的不同计算时,使用枚举是实现策略模式的一种快速而清晰的方法。

public enum Operation {
PLUS {
public double calc(double a, double b) {
return a + b;
}
},
TIMES {
public double calc(double a, double b) {
return a * b;
}
}
...


public abstract double calc(double a, double b);
}

显然,策略方法声明以及每个实现的一个实例都定义在一个类/文件中。

看看lambdaj

http://code.google.com/p/lambdaj/

特别是它新的闭包特征

http://code.google.com/p/lambdaj/wiki/Closures

你会发现一种非常易读的方式来定义闭包或函数指针,而无需创建无意义的接口或使用丑陋的内部类

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

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

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

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

@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);

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

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

现在非常流行的谷歌番石榴库有一个通用的函数谓词对象,它们已经在API的许多部分中使用了这个对象。

哇,为什么不创建一个Delegate类,这并不难,因为我已经为java做了,并使用它来传递参数,其中T是返回类型。我很抱歉,但作为一个c++ / c#程序员,一般只学习java,我需要函数指针,因为它们非常方便。如果你熟悉任何处理方法信息的类,你可以这样做。在java库中是java。lang。reflect。method。

如果你总是使用一个接口,你就必须实现它。在事件处理中,确实没有更好的方法来从处理程序列表中注册/取消注册,但对于委托,你需要传递函数而不是值类型,使委托类处理它outclasses一个接口。

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

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.lang.reflect.Method Function = Class.forName(String classPath).getMethod(String method, Class[] params);

对于接受一个double作为参数的函数。

在具体情况下,我用

java.lang.reflect.Method Function = Class.forName("be.qan.NN.ActivationFunctions").getMethod("sigmoid", double.class);

并在以后更复杂的情况下调用它

return (java.lang.Double)this.Function.invoke(null, args);


java.lang.Object[] args = new java.lang.Object[] {activity};
someOtherFunction() + 234 + (java.lang.Double)Function.invoke(null, args);

activity是任意的双精度值。我正在考虑可能做得更抽象和一般化一点,就像SoftwareMonkey所做的那样,但目前我对它的方式很满意。三行代码,没有必要的类和接口,还不错。

方法引用使用::操作符

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

applyAsInt1是一个功能接口。它的抽象方法applyAsInt2接受两个int作为参数,并返回一个intapplyAsInt3也接受两个int并返回一个int。在本例中,A.method(Math::max);使parameter.applyAsInt将其两个输入值发送给Math.max,并返回该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 8Java语言规范§15.13

如果有人试图传递一个函数,该函数使用一组参数来定义其行为,但使用另一组参数来执行,就像Scheme的:

(define (function scalar1 scalar2)
(lambda (x) (* x scalar1 scalar2)))

看到在Java中传递带有参数定义行为的函数

新的Java 8 功能接口方法引用使用::操作符。

Java 8能够使用"@功能界面"指针维护方法引用(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();
}
}

那么,我们得到了什么?

  1. 函数接口——这是一个接口,不管有没有@FunctionalInterface注释,它只包含一个方法声明。
  2. 方法引用——这只是一种特殊的语法,看起来像这样,objectInstance: methodName,不多不少。
  3. 用法示例-只是一个赋值操作符,然后调用接口方法。

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

因为所有其他类似的函数指针都不利于代码的可读性和理解能力。然而,直接方法引用有时很方便,例如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上面提到的,它具有类似的功能和语法。

如需进一步研究,请参阅java8备考单

感谢戴帽子的家伙提供的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

在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);

没有一个Java 8的答案给出了一个完整的、内聚的例子,所以它就来了。

声明接受“函数指针”的方法如下:

void doCalculation(Function<Integer, String> calculation, int parameter) {
final String result = calculation.apply(parameter);
}

通过为函数提供lambda表达式来调用它:

doCalculation((i) -> i.toString(), 2);

开源安全镜项目将上面提到的一些解决方案泛化到一个库中,该库将函数、委托和事件添加到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);
}

注意:

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

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