Bil*_*ard 299 java closures function-pointers
我有一个大约十行代码的方法.我想创建更多完全相同的方法,除了一个会改变一行代码的小计算.这是传递函数指针以替换该行的完美应用程序,但Java没有函数指针.什么是我最好的选择?
sbl*_*ndy 269
匿名内部阶级
假设您希望使用String返回的param 传入函数int.
首先,如果不能重用现有的接口,则必须定义一个以函数为唯一成员的接口.
interface StringFunction {
int func(String param);
}
Run Code Online (Sandbox Code Playgroud)
获取指针的方法只接受这样的StringFunction实例:
public void takingMethod(StringFunction sf) {
int i = sf.func("my string");
// do whatever ...
}
Run Code Online (Sandbox Code Playgroud)
会像这样被称为:
ref.takingMethod(new StringFunction() {
public int func(String param) {
// body
}
});
Run Code Online (Sandbox Code Playgroud)
编辑:在Java 8中,您可以使用lambda表达式调用它:
ref.takingMethod(param -> bodyExpression);
Run Code Online (Sandbox Code Playgroud)
jav*_*ook 28
当您可以在该行中执行预定义数量的不同计算时,使用枚举是一种快速但清晰的方法来实现策略模式.
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);
}
Run Code Online (Sandbox Code Playgroud)
显然,策略方法声明以及每个实现的恰好一个实例都在单个类/文件中定义.
rcr*_*ick 24
您需要创建一个接口,提供您想要传递的功能.例如:
/**
* 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);
}
Run Code Online (Sandbox Code Playgroud)
然后,当您需要传递函数时,您可以实现该接口:
List<Integer> result = CollectionUtilities.map(list,
new Function1<Integer, Integer>() {
@Override
public Integer eval(Integer a) {
return a * a;
}
});
Run Code Online (Sandbox Code Playgroud)
最后,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;
}
Run Code Online (Sandbox Code Playgroud)
如果您不需要传入参数,通常可以使用Runnable而不是自己的接口,或者您可以使用各种其他技术使参数计数不那么"固定",但通常需要权衡类型安全性.(或者你可以覆盖你的函数对象的构造函数,以这种方式传递params ..有很多方法,有些方法在某些情况下更好.)
The*_*Hat 17
::运算符的方法引用您可以在方法接受函数接口的方法参数中使用方法引用.功能接口是仅包含一个抽象方法的任何接口.(功能接口可能包含一个或多个默认方法或静态方法.)
IntBinaryOperator是一个功能界面.它的抽象方法,applyAsInt接受两个ints作为参数并返回一个int.Math.max也接受两个ints并返回一个int.在这个例子中,A.method(Math::max);make 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);
}
}
Run Code Online (Sandbox Code Playgroud)
import java.lang.Math;
class B {
public static void main(String[] args) {
A.method(Math::max);
}
}
Run Code Online (Sandbox Code Playgroud)
一般来说,你可以使用:
method1(Class1::method2);
Run Code Online (Sandbox Code Playgroud)
代替:
method1((arg1, arg2) -> Class1.method2(arg1, arg2));
Run Code Online (Sandbox Code Playgroud)
这是短的:
method1(new Interface1() {
int method1(int arg1, int arg2) {
return Class1.method2(arg1, agr2);
}
});
Run Code Online (Sandbox Code Playgroud)
有关更多信息,请参阅Java 8中的::(双冒号)运算符和Java语言规范§15.13.
Tof*_*eer 15
你也可以这样做(在某些RARE场合有意义).问题(这是一个大问题)是你失去了使用类/接口的所有类型安全性,你必须处理不存在该方法的情况.
它确实具有"好处",您可以忽略访问限制并调用私有方法(示例中未显示,但您可以调用编译器通常不会让您调用的方法).
同样,这是一种罕见的情况,这是有道理的,但在这种情况下,它是一个很好的工具.
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);
}
}
Run Code Online (Sandbox Code Playgroud)
小智 11
使用运算符的新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();
}
}
Run Code Online (Sandbox Code Playgroud)
那么,我们在这里有什么?
你应该只为那些听众使用功能接口而且只能这样做!
因为所有其他此类函数指针对于代码可读性和理解能力都非常糟糕.但是,直接方法引用有时会派上用场,例如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);
Run Code Online (Sandbox Code Playgroud)
对于早期的Java版本,您应该尝试Guava Libraries,它具有与Adrian Petrescu上面提到的类似的功能和语法.
有关其他研究,请参阅Java 8 Cheatsheet
并感谢The Guy with the Hat for the Java LanguageSpecification§15.13链接.
@ sblundy的答案很棒,但匿名内部类有两个小缺陷,主要是它们往往不可重用,而辅助是一个庞大的语法.
好处是他的模式扩展为完整的类而没有主类(执行计算的那个)的任何改变.
当您实例化一个新类时,您可以将参数传递给该类,该类可以作为等式中的常量 - 因此,如果您的一个内部类看起来像这样:
f(x,y)=x*y
Run Code Online (Sandbox Code Playgroud)
但有时你需要一个:
f(x,y)=x*y*2
Run Code Online (Sandbox Code Playgroud)
也许是第三个:
f(x,y)=x*y/2
Run Code Online (Sandbox Code Playgroud)
而不是制作两个匿名内部类或添加"passthrough"参数,您可以创建一个实例化的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);
Run Code Online (Sandbox Code Playgroud)
它只是将常量存储在类中,并在接口中指定的方法中使用它.
事实上,如果知道你的函数不会被存储/重用,你可以这样做:
InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f.setConstant(2.0);
calculateUsing(f);
f.setConstant(0.5);
calculateUsing(f);
Run Code Online (Sandbox Code Playgroud)
但是不可变的类更安全 - 我无法想出让这样的类变得可变的理由.
我真的只发布这个因为每当我听到匿名的内部课时我都会畏缩 - 我看到很多冗余的代码都是"必需的",因为程序员做的第一件事就是匿名,因为他应该使用一个真正的类而且从不重新思考他的决定.
用 Java 编程时我真正怀念的一件事是函数回调。需要这些不断出现的一种情况是递归处理层次结构,您希望为每个项目执行某些特定操作。就像遍历目录树或处理数据结构一样。我内心的极简主义者讨厌必须定义一个接口,然后为每个特定情况定义一个实现。
有一天,我发现自己在想为什么不呢?我们有方法指针——Method 对象。通过优化 JIT 编译器,反射调用确实不再带来巨大的性能损失。除了将文件从一个位置复制到另一个位置之外,反射方法调用的成本变得微不足道。
当我想得更多时,我意识到 OOP 范式中的回调需要将一个对象和一个方法绑定在一起——输入回调对象。
查看我的基于反射的Java 回调解决方案。免费供任何使用。
| 归档时间: |
|
| 查看次数: |
87331 次 |
| 最近记录: |