当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《什么是Java中函数指针的最接近的替代品?》经验,为你挑选了11个好方法。

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



1> sblundy..:

匿名内部阶级

假设您希望使用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);


顺便说一句,这是"命令模式"的一个例子.http://en.wikipedia.org/wiki/Command_Pattern
@Ogre Psalm33这种技术也可以是战略模式,具体取决于你如何使用它.[策略模式和命令模式之间的区别](http://stackoverflow.com/questions/3883692/strategy-pattern-vs-command-pattern).
这是Java 5,6和7的闭包实现http://mseifed.blogspot.se/2012/09/closure-implementation-for-java-5-6-and.html它包含了所有可能要求的内容. ..我认为它非常棒!

2> Blair Conrad..:

对于每个"函数指针",我将创建一个实现计算的小型仿函数类.定义所有类将实现的接口,并将这些对象的实例传递给更大的函数.这是" 命令模式 "和" 策略模式 "的组合.

@ sblundy的例子很好.



3> javashlook..:

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

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

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



4> rcreswick..:

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

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

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

}

然后,当您需要传递函数时,您可以实现该接口:

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

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

   public static  Map zipWith(Function2 fn, 
         Map m1, Map m2, Map results){
      Set keySet = new HashSet();
      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 ..有很多方法,有些方法在某些情况下更好.)


这个"答案"更多地涉及*问题集*而不是*解决方案集.*☹

5> The Guy with..:

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

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

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



6> TofuBeer..:

你也可以这样做(在某些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);
    }
}



7> Peter Lawrey..:

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


+1因为有时候这就足够了.

8> Dave L...:

您可能也有兴趣了解Java 7涉及闭包的工作:

Java中当前的闭包状态是什么?

http://gafter.blogspot.com/2006/08/closures-for-java.html
http://tech.puredanger.com/java7/#closures



9> 小智..:

使用运算符的新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的接口,带注释或不带有@FunctionalInterface,它只包含一个方法声明.

    方法参考 - 这只是特殊的语法,看起来像这样,objectInstance :: methodName,仅此而已.

    用法示例 - 只是一个赋值运算符,然后是接口方法调用.

你应该只为那些听众使用功能接口而且只能这样做!

因为所有其他此类函数指针对于代码可读性和理解能力都非常糟糕.但是,直接方法引用有时会派上用场,例如foreach.

有几个预定义的功能接口:

Runnable              -> void run( );
Supplier           -> T get( );
Consumer           -> void accept(T);
Predicate          -> boolean test(T);
UnaryOperator      -> T apply(T);
BinaryOperator -> R apply(T, U);
Function         -> R apply(T);
BiFunction     -> R apply(T, U);
//... and some more of it ...
Callable           -> V call() throws Exception;
Readable              -> int read(CharBuffer) throws IOException;
AutoCloseable         -> void close() throws Exception;
Iterable           -> Iterator iterator();
Comparable         -> int compareTo(T);
Comparator         -> int compare(T,T);

对于早期的Java版本,您应该尝试Guava Libraries,它具有与Adrian Petrescu上面提到的类似的功能和语法.

有关其他研究,请参阅Java 8 Cheatsheet

并感谢The Guy with the Hat for the Java LanguageSpecification§15.13链接.


"*因为所有其他......对于代码可读性来说真的很糟糕*"是一个完全毫无根据的声明,而且是错误的.

10> Bill K..:

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

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

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



11> Adrian Petre..:

Google Guava库变得非常流行,它们具有通用的Function和Predicate对象,它们已经在API的许多部分中工作.

推荐阅读
ar_wen2402851455
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有