我有一个方法,大约有十行代码。我想创建更多的方法来做完全相同的事情,除了一个会改变一行代码的小计算。这是一个传递函数指针来替换这一行的完美应用程序,但是Java没有函数指针。我最好的选择是什么?
当前回答
谷歌Guava库正在变得非常流行,它有一个通用的Function和Predicate对象,它们已经在API的许多部分中使用了这些对象。
其他回答
没有一个Java 8的答案给出了一个完整的、内聚的例子,所以它就来了。
声明接受“函数指针”的方法如下:
void doCalculation(Function<Integer, String> calculation, int parameter) {
final String result = calculation.apply(parameter);
}
通过为函数提供lambda表达式来调用它:
doCalculation((i) -> i.toString(), 2);
当在一行中可以执行预定义数量的不同计算时,使用枚举是实现策略模式的一种快速而清晰的方法。
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);
}
显然,策略方法声明以及每个实现的一个实例都定义在一个类/文件中。
@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);
但是不可变的类更安全——我找不到一个理由让这样的类成为可变的。
我之所以发布这篇文章,是因为每当我听到匿名的内部类时,我就会感到畏缩——我看到过很多“必需”的冗余代码,因为程序员做的第一件事就是在他应该使用实际类的时候使用匿名,并且从来没有重新考虑过他的决定。
您需要创建一个接口,该接口提供您希望传递的函数。例如:
/**
* 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而不是自己的接口,或者您可以使用各种其他技术使参数计数不那么“固定”,但这通常是与类型安全的权衡。(或者你可以重写你的函数对象的构造函数,以这种方式传递参数。有很多方法,其中一些在特定情况下效果更好。)
方法引用,使用::操作符
当方法接受函数接口时,可以在方法参数中使用方法引用。函数接口是任何只包含一个抽象方法的接口。(一个功能接口可以包含一个或多个默认方法或静态方法。)
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中的::(双冒号)操作符。
推荐文章
- c#和Java的主要区别是什么?
- 什么是NullPointerException,我如何修复它?
- 在Java中使用“final”修饰符
- 无法在Flutter上找到捆绑的Java版本
- 如何在Kotlin解析JSON ?
- 如何在新的材质主题中改变背面箭头的颜色?
- 如何设置OkHttp连接超时
- 用于桌面应用程序的Swing vs JavaFx
- 为什么这个Java程序会终止,尽管它显然不应该(也没有)终止?
- 使用split("|")按管道符号拆分Java字符串
- 当内存不足导致抛出OutOfMemoryError时会发生什么?
- 检查ArrayList中是否存在值
- 我们能在Java中创建无符号字节吗
- 同步vs锁定
- 在ExecutorService的提交和执行之间进行选择