首页 文章

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

提问于
浏览
297

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

21 回答

  • 11

    听起来像是一种策略模式 . 查看fluffycat.com Java模式 .

  • 32

    匿名内部阶级

    假设您希望使用 String param传入一个返回 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);
    
  • 16

    对于每个"function pointer",我会创建一个小functor class来实现您的计算 . 定义所有类将实现的接口,并将这些对象的实例传递给更大的函数 . 这是“command pattern ", and " strategy pattern”的组合 .

    @ sblundy的例子很好 .

  • 12

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

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

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

  • 3

    您需要创建一个接口,提供您想要传递的功能 . 例如:

    /**
     * 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而不是您自己的接口,或者您可以使用各种其他技术使参数计数不那么“固定”,但它通常是类型安全的权衡 . (或者你可以覆盖你的函数对象的构造函数,以这种方式传递params ..有很多方法,有些方法在某些情况下更好 . )

  • 1

    使用::运算符的方法引用

    您可以在方法接受函数接口的方法参数中使用方法引用 . 功能接口是仅包含一个抽象方法的任何接口 . (功能接口可能包含一个或多个默认方法或静态方法 . )

    IntBinaryOperator是一个功能界面 . 它的抽象方法applyAsInt接受两个 int s作为其参数并返回 int . Math.max也接受两个 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);
        }
    });
    

    有关更多信息,请参阅:: (double colon) operator in Java 8Java Language Specification §15.13 .

  • 6

    你也可以这样做(在某些情况下这是有意义的) . 问题(这是一个大问题)是你失去了使用类/接口的所有类型安全性,你必须处理不存在该方法的情况 .

    它确实具有“好处”,您可以忽略访问限制并调用私有方法(示例中未显示,但您可以调用编译器通常不会让您调用的方法) .

    同样,这是一种罕见的情况,这是有道理的,但在这种情况下,它是一个很好的工具 .

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

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

  • 4

    New Java 8 Functional Interfaces and Method References using the :: operator.

    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的接口,带注释或不带有@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 Libraries,它具有与Adrian Petrescu上面提到的类似的功能和语法 .

    如需进一步研究,请查看Java 8 Cheatsheet

    并感谢The Guy with the Hat for the Java Language Specification §15.13 link .

  • 28

    @ sblundy的答案很棒,但匿名内部类有两个小缺陷,主要是它们往往不可重用,而辅助是一个庞大的语法 .

    好处是他的模式扩展为完整的类而没有主类(执行计算的那个)的任何改变 .

    当您实例化一个新类时,您可以将参数传递给该类,该类可以作为等式中的常量 - 因此,如果您的一个内部类看起来像这样:

    f(x,y)=x*y
    

    但有时你需要一个:

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

    也许是第三个:

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

    而不是制作两个匿名内部类或添加“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);
    

    它只是将常量存储在类中,并在接口中指定的方法中使用它 .

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

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

    但是不可变的类更安全 - 我无法想出让这样的类变得可变的理由 .

    我真的只发布这个因为每当我听到匿名的内部课时我都会畏缩 - 我看到很多冗余的代码都是“必需的”,因为程序员做的第一件事就是匿名,因为他应该使用实际的类而且从不重新思考他的决定 .

  • 2

    正在变得非常受欢迎的Google Guava libraries有一个通用的FunctionPredicate对象,它们已经在API的许多部分中使用了它们 .

  • 4

    在Java编程时我真正想念的一件事是函数回调 . 一种情况是,这些需要保持自我呈现的是递归处理层次结构,您希望为每个项目执行某些特定操作 . 就像走一个目录树,或者处理数据结构一样 . 我内心的极简主义者讨厌定义一个接口,然后为每个特定情况定义一个实现 .

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

    当我更多地考虑它时,我意识到OOP范例中的回调需要将对象和方法绑定在一起 - 输入Callback对象 .

    查看基于反射的Callbacks in Java解决方案 . 免费用于任何用途 .

  • 1

    哦,这个帖子已经够老了,所以很可能我的答案对这个问题没有帮助 . 但是由于这个线程帮助我找到了我的解决方案,无论如何我会把它放在这里 .

    我需要使用具有已知输入和已知输出的可变静态方法(均为 double ) . 那么,知道方法包和名称,我可以按如下方式工作:

    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是任意的double值 . 正如SoftwareMonkey所做的那样,我正在考虑做一些更抽象和概括的事情,但目前我很满意它的方式 . 三行代码,没有必要的类和接口,这不是太糟糕 .

  • 24

    为没有函数数组的接口做同样的事情:

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

    看看lambdaj

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

    特别是它的新封闭功能

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

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

  • 13

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

    如果您始终使用界面,则始终必须实现它 . 在事件处理中,从处理程序列表中注册/取消注册实际上没有更好的方法,但是对于需要传递函数而不是值类型的委托,使委托类为外部接口处理它 .

  • 3

    Java 8的答案都没有给出一个完整的,有凝聚力的例子,所以它来了 .

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

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

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

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

    如果有人正在努力传递一个函数,该函数需要一组参数来定义它的行为,而另一组参数要执行,比如Scheme的:

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

    Pass Function with Parameter-Defined Behavior in Java

  • 4

    从Java8开始,您可以使用lambdas,它也在官方SE 8 API中有库 .

    Usage: 您需要使用只有一个抽象方法的接口 . 制作一个它的实例(你可能想使用已经提供的一个java SE 8),如下所示:

    Function<InputType, OutputType> functionname = (inputvariablename) {
    ... 
    return outputinstance;
    }
    

    有关更多信息,请查看文档:https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

  • 4

    在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 expression,它可以用作:

    list.sort((a, b) ->  { a.isBiggerThan(b) } );
    

    其中isBiggerThan是 CustomClass 中的方法 . 我们也可以在这里使用方法引用:

    list.sort(MyClass::isBiggerThan);
    

相关问题