【问题标题】:Using Scala from Java: passing functions as parameters在 Java 中使用 Scala:将函数作为参数传递
【发布时间】:2011-07-01 07:45:56
【问题描述】:

考虑以下 Scala 代码:

package scala_java
object MyScala {
  def setFunc(func: Int => String) {
    func(10)
  }
}

现在在 Java 中,我希望使用 MyScala 作为:

package scala_java;
public class MyJava {
    public static void main(String [] args) {
        MyScala.setFunc(myFunc);  // This line gives an error
    }
    public static String myFunc(int someInt) {
        return String.valueOf(someInt);
    }
}

但是,上述方法不起作用(正如预期的那样,因为 Java 不允许函数式编程)。在 Java 中传递函数最简单的解决方法是什么?我想要一个适用于具有任意数量参数的函数的通用解决方案。

编辑:Java 8 的语法是否比下面讨论的经典解决方案更好?

【问题讨论】:

    标签: java scala scala-java-interop


    【解决方案1】:

    scala.runtime 包中,有名为AbstractFunction1 的抽象类,以此类推。要从 Java 中使用它们,您只需覆盖 apply,如下所示:

    Function1<Integer, String> f = new AbstractFunction1<Integer, String>() {
        public String apply(Integer someInt) {
            return myFunc(someInt);
        }
    };
    

    如果您使用的是 Java 8 并希望为此使用 Java 8 lambda 语法,请查看https://github.com/scala/scala-java8-compat

    【讨论】:

    • 谢谢!你的回答是最有帮助的!在尝试实现 Function1 时,Java 告诉我必须实现几十种方法 - Function1 有几种基本类型的特化。
    • 您正在寻找的答案:) 另请查看twitter.github.io/scala_school/java.html
    • 请注意,如果您正在为 Android 或 iOS 进行编译,您将无法使用 scala-java8-compat,因为 retrolambda 只能修改您拥有的源文件。源代码也没有 lambda 桥类(JFunction0 等),因为它们是从 sbt 直接生成到 .class 文件的代码。可能有一种方法可以将 .class 文件包含在您的项目中,retrolambda 可以在其中修改它们,但我没有投入精力去解决这个问题。
    • 如果你的函数参数不带参数,你需要用BoxedUnit键入一个AbstractFunction1;同样,如果它返回 Unit,则需要使用 BoxedUnit 键入返回值并在 lambda 中返回 BoxedUnit.UNIT。即new AbstractFunction1&lt;BoxedUnit, BoxedUnit&gt;() { public BoxedUnit apply(BoxedUnit unit) { /* . . . */ return BoxedUnit.UNIT; } }
    【解决方案2】:

    您必须在 Java 中手动实例化 Function1。比如:

    final Function1<Integer, String> f = new Function1<Integer, String>() {
        public int $tag() {
            return Function1$class.$tag(this);
        }
    
        public <A> Function1<A, String> compose(Function1<A, Integer> f) {
            return Function1$class.compose(this, f);
        }
    
        public String apply(Integer someInt) {
            return myFunc(someInt);
        }
    };
    MyScala.setFunc(f);
    

    这取自Daniel Spiewak’s “Interop Between Java and Scala” article

    【讨论】:

    • 请务必查看文章和丹尼尔在 cmets 中的 FunUtils 类,这有助于删除 $tagcompose 所需的一些样板。
    • 信息量很大。我以前看过这篇文章,但没有读过cmets。
    • @Jean-PhilippePellet 似乎 Daniel Spiewak 的链接已损坏。
    • @Gautham 查看赛斯的答案,它比我的要好。 Daniel 的链接现在已经有 10 年的历史了……
    【解决方案3】:

    对我来说最简单的方法是定义一个 java 接口,例如:

    public interface JFunction<A,B> {
      public B compute( A a );
    }
    

    然后修改你的 scala 代码,重载 setFunc 以接受 JFunction 对象,例如:

    object MyScala {
      // API for scala
      def setFunc(func: Int => String) {
        func(10)
      }
      // API for java
      def setFunc(jFunc: JFunction[Int,String]) {
        setFunc( (i:Int) => jFunc.compute(i) )
      }
    }
    

    你自然会使用 scala 中的第一个定义,但仍然可以使用 java 中的第二个:

    public class MyJava {
      public static void main(String [] args) {
        MyScala.setFunc(myFunc);  // This line gives an error
      }
    
      public static final JFunction<Integer,String> myFunc = 
        new JFunction<Integer,String>() {
          public String compute( Integer a ) {
            return String.valueOf(a);
          }
        };
    
    }
    

    【讨论】:

    • @Jus12。谢谢,已经解决了。
    • 实际上,Scala 的函数已经在幕后作为 SAM 类型实现。因此,您可以直接实现 FunctionN(根据 Jean-Philippe 的回答),而无需引入您自己的 JFunction 类型。
    • 我不同意,虽然您可以直接从 java 实例化一个 scala 函数,但生成的代码相当难看。如果你有时只是想访问一些 scala 代码,没关系。但是如果你提供一个 java API,你会通过添加一些更高级别的接口和为 java API 设计的 scala 方法来获得更好的代码。例如,这将有助于应对缺乏隐式解决方案。
    • 只要你有 Scala 源码就可以了。
    • @paradigmatic - 即便如此,使用 Google guava 中的 Function 之类的东西,而不是重新发明轮子,仍有一个强有力的论据。
    【解决方案4】:

    这是我尝试的解决方案,一个小库:https://github.com/eirslett/sc8

    您将 Java 8 lambda 包装在 F(...) 中,然后将其转换为 Scala 函数。

    【讨论】:

      猜你喜欢
      • 2016-11-22
      • 1970-01-01
      • 2013-05-23
      • 2017-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-27
      相关资源
      最近更新 更多