使用Scala从Java调用:将函数作为参数传递

49

考虑以下 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是否有比下面讨论的经典解决方案更好的语法?

4个回答

75

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


2
谢谢!你的回答是所有中最有帮助的!我正在尝试实现Function1,但Java告诉我我必须实现数十个方法 - Function1有几种原始类型的特殊化。 - jcsahnwaldt Reinstate Monica
1
这就是你要找的答案 :) 还可以查看 http://twitter.github.io/scala_school/java.html - alexr
请注意,如果您正在为Android或iOS编译,您将无法使用scala-java8-compat,因为retrolambda只能修改您拥有源文件的内容。源代码也没有Lambda桥接类(例如JFunction0),因为它们是从sbt直接生成到.class文件中的。可能有一种方法可以在项目中包含.class文件,使retrolambda可以修改它们,但我还没有投入精力去找出来。 - voxoid
1
如果您的函数参数不需要参数,则需要使用BoxedUnit类型的AbstractFunction1进行输入;同样,如果它返回Unit,则需要使用BoxedUnit类型的返回值,并在lambda中返回BoxedUnit.UNIT。例如:new AbstractFunction1<BoxedUnit, BoxedUnit>() { public BoxedUnit apply(BoxedUnit unit) { /* . . . */ return BoxedUnit.UNIT; } } - voxoid

27

在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的" Java和Scala之间的互操作性"文章.


4
一定要查看文章和评论中的Daniel的FunUtils类,它有助于删除$tagcompose所需的一些样板代码。 - Jean-Philippe Pellet
那非常有用。我以前看过这篇文章,但没有看评论。 - Jus12
1
@Jean-PhilippePellet,看起来Daniel Spiewak的链接已经失效了。 - Gautham
1
@Gautham 看看 Seth 的回答,比我的好。Daniel 的链接现在已经有大约10年了... - Jean-Philippe Pellet

7

对我来说最简单的方法是定义一个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。谢谢,问题已经解决了。 - paradigmatic
实际上,Scala的函数已经在幕后作为SAM类型实现。因此,您可以直接实现FunctionN(如Jean-Philippe的答案所述),而无需引入自己的JFunction类型。 - Kevin Wright
我不同意,虽然你可以直接从Java实例化Scala函数,但结果代码相当丑陋。如果你只是想有时访问一些Scala代码,那么这样做还可以。但是,如果你要提供一个Java API,通过添加一些高级接口和专为Java API设计的Scala方法,你将拥有更好的代码。这将有助于解决隐式解析的缺乏等问题。 - paradigmatic
只要你有Scala源代码,这就没问题。 - Jus12
即使如此,有一个很有力的论点支持使用类似于Google guava中的“Function”类型,而不是重新发明轮子。 - Kevin Wright
@Kevin Wright。当然,甚至可以使用FunctionalJava。那只是一个可以做的例子。 - paradigmatic

0

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

你可以将Java 8 lambda表达式包装在F(...)中,然后它会被转换为Scala函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接