One More Thing To Consider When Calling Scala From Java


One more thing to consider when calling Scala function from Java

Let’s say we have a few functions that work identical when called from Scala:

object Hello {

  type Str2Int = String => Int

  def creator: () => Str2Int = () => _.length

  def f1: Str2Int = creator()

  def f2: Str2Int =  (s) => creator()(s)

  def f3(s: String) = creator()(s)

  println(f1("42"))   // 2
  println(f2("422"))  // 3
  println(f3("4222")) // 4
}

However, when you try to call them from Java, you’ll find out that only f3 can be applied directly to a String. The other 2 will need to be called without arguments, and that will return Function1<String, Object>, which you can then call and cast the result to Int. Why? Let’s look inside into the generated class file with javap.

javap ./target/scala-2.11/classes/Hello.class
Compiled from "Hello.scala"
public final class Hello {
  public static int f3(java.lang.String);
  public static scala.Function1<java.lang.String, java.lang.Object> f2();
  public static scala.Function1<java.lang.String, java.lang.Object> f1();
  public static scala.Function0<scala.Function1<java.lang.String, java.lang.Object>> creator();
}

Only f3 ends up being a “normal” class member with the specified input/output type. The others return Scala’s Function1, return type of which gets erased. So, it’s not even Integer or int (actually it can’t int because it’s a type parameter). Which makes calling this code from Java especially ugly. Something like:

Integer v = (Integer)Hello.f1().apply("42”);

It’s funny that I’ve run into this issue exactly when the compiler couldn’t help: I’ve generated a function which was supposed to work as a handler for AWS Lambda, and probably it was called using reflection, hence I received no errors at all, the code just hadn’t been executed.

There is scala-java8-compat something to make thins