提问人:Bill the Lizard 提问时间:9/24/2008 最后编辑:Bill the Lizard 更新时间:9/7/2020 访问量:92420
Java 中函数指针的最接近的替代品是什么?
What's the nearest substitute for a function pointer in Java?
问:
我有一个大约十行代码的方法。我想创建更多执行完全相同操作的方法,除了将更改一行代码的小计算。这是一个完美的应用程序,用于传入函数指针来替换那一行,但 Java 没有函数指针。我最好的选择是什么?
答:
匿名内部类
假设您希望传递一个函数,其中包含返回 .
首先,如果不能重用现有接口,则必须定义一个接口,该接口作为其唯一成员。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 而不是你自己的接口,或者你可以使用各种其他技术来使参数计数不那么“固定”,但这通常是与类型安全的权衡。(或者,您可以重写函数对象的构造函数,以这种方式传入参数。有很多方法,有些方法在某些情况下效果更好。
评论
您可能还有兴趣了解 Java 7 中涉及闭包的工作:
http://gafter.blogspot.com/2006/08/closures-for-java.html http://tech.puredanger.com/java7/#closures
评论
对我来说,这听起来像是一种策略模式。查看 fluffycat.com Java 模式。
您也可以这样做(在某些罕见的场合是有道理的)。问题(这是一个大问题)是你失去了使用类/接口的所有类型安全,你必须处理方法不存在的情况。
它确实有一个“好处”,即您可以忽略访问限制并调用私有方法(示例中未显示,但您可以调用编译器通常不允许调用的方法)。
同样,这在极少数情况下是有道理的,但在那些情况下,它是一个很好的工具。
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);
}
}
评论
如果您只有一行不同,则可以添加一个参数,例如 flag 和 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 范式中的回调需要将对象和方法绑定在一起 - 输入 Callback 对象。
查看我的基于反射的 Java 回调解决方案。免费供任何使用。
@sblundy的答案很好,但匿名内部类有两个小缺陷,首先是它们往往不可重用,其次是语法笨重。
好消息是,他的模式可以扩展到完整的类中,而主类(执行计算的类)没有任何变化。
当你实例化一个新类时,你可以将参数传递到该类中,该类可以充当方程式中的常量 - 因此,如果你的一个内部类如下所示:
f(x,y)=x*y
但有时你需要一个:
f(x,y)=x*y*2
也许还有第三个是:
f(x,y)=x*y/2
您可以创建单个实例化为的 ACTUAL 类,而不是创建两个匿名内部类或添加“直通”参数:
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);
但是不可变类更安全——我想不出一个理由来使这样的类变得可变。
我真的只是因为每当我听到匿名的内部类时我都会畏缩——我看到很多冗余的代码是“必需的”,因为程序员做的第一件事就是匿名,当他应该使用一个实际的类并且从未重新考虑他的决定时。
评论
Google Guava 库变得非常流行,它有一个通用的 Function 和 Predicate 对象,它们已经将其用于其 API 的许多部分。
评论
哇,为什么不创建一个 Delegate 类,考虑到我已经为 java 做过,这并不难,并用它来传入参数,其中 T 是返回类型。很抱歉,但作为一个C++/C#程序员,一般只是学习java,我需要函数指针,因为它们非常方便。如果您熟悉任何处理方法信息的类,则可以这样做。在 java 库中,这将是 java.lang.reflect.method。
如果你总是使用一个接口,你总是必须实现它。在事件处理中,确实没有更好的方法可以从处理程序列表中注册/注销,但对于需要传入函数而不是值类型的委托,制作一个委托类来处理它,以处理外级接口。
评论
要在没有函数数组接口的情况下执行相同的操作:
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); } }
评论
oK,这个线程已经足够老了,所以很可能我的答案对这个问题没有帮助。但是由于这个线程帮助我找到了解决方案,无论如何我都会把它放在这里。
我需要使用具有已知输入和已知输出(均为双精度)的可变静态方法。因此,在知道方法包和名称后,我可以按如下方式工作:
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 那样更抽象地概括它,但目前我对它的方式感到满意。三行代码,不需要类和接口,这还不错。
评论
code
使用运算符的方法引用::
可以在方法参数中使用方法引用,其中方法接受函数接口。函数式接口是仅包含一个抽象方法的任何接口。(函数接口可能包含一个或多个默认方法或静态方法。
IntBinaryOperator
是一个函数式接口。它的抽象方法 applyAsInt
接受两个 s 作为其参数,并返回一个 .Math.max
也接受两个 s 并返回一个 .在此示例中,make 将其两个输入值发送到 并返回 .int
int
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 8 中的 :: (双冒号) 运算符和 Java 语言规范 §15.13。
如果有人正在努力传递一个函数,该函数需要一组参数来定义其行为,但需要执行另一组参数,例如 Scheme 的:
(define (function scalar1 scalar2)
(lambda (x) (* x scalar1 scalar2)))
使用 ::
运算符的新 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();
}
}
那么,我们这里有什么?
- 函数接口 - 这是带有注释或不带 @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 备忘单
感谢 The Guy with The Hat 提供的 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);
评论
另一方面,::
运算符...