Friday, May 01, 2009

Scala & Java interoperability: statics

Scala and Java interoperability is great. In most cases its stemless and its a great way to introduce Scala into existing code base. Actually, it has great benefits as I'm slowly absorbing Scala to one of the modules at LinkedIn. Artifacts can inherit and call each other in the same compilation unit in a transparent way. Well... almost

There is a small surprising factor when you get to statics and it has two parts.
Part 1: Scala <- Java
A Java class that extends another Java class that contains static artifacts can use those statics since they are inherited with the super class. A classic example is JUnit TestCase by the most popular Java test framework. Since I am trying to use the existing JUnit based test framework my Scala test classes are extending TestCase, and like in the Java tests I wish to use the assert* methods in Assert. But here comes the surprise, Scala will not recognize those.
Consider the following example. Here is a simple Java class with two methods, one of them is static:

package test1.java;
public class SuperTest{
protected String superMethod() {return "super";}
public static String superStatic() {return "super static";}
}
And a Scala class extending it
package test1
import test1.java.SuperTest
class Test extends SuperTest{
def useSuper = println(superMethod)
def useSuperStatic = println(superStatic)
}
Which gives us
.../src/test1/Test.scala:7: error: not found: value superStatic
def useSuperStatic = println(superStatic)
Note that scalac didn't have problem with using superMethod, its only superStatic who had the problem. Adding this import would solve the problem
import test1.java.SuperTest._


Part 2: Scala -> Java
There are no statics in Scala's syntax. The closest thing to static is Scala's Object which is a singleton. Actually, Scala compiles the object's artifacts to statics so from Java's point of few a Scala object is a final class (i.e. can't extend) with static members and methods. For example
package test1
object Test{
def scalaStatic = "scala static"
}
gives us
javap bin/test1/Test
Compiled from "Test.scala"
public final class test1.Test extends java.lang.Object{
public static final java.lang.String scalaStatic();
public static final int $tag() throws java.rmi.RemoteException;
}
javap bin/test1/Test$
Compiled from "Test.scala"
public final class test1.Test$ extends java.lang.Object implements scala.ScalaObject{
public static final test1.Test$ MODULE$;
public static {};
public test1.Test$();
public java.lang.String scalaStatic();
public int $tag() throws java.rmi.RemoteException;
}
So we can do the following from Java
package test1.java;
import test1.Test;
public class JavaTest{
public String usingScalaStatic() {return Test.scalaStatic();}
}
It works fine and is a good showcase to how to integrate nicely between the languages. But now assume that someone decides to add a Scala class named Test alongside the Test object
package test1
class Test{
def hi = println("hi")
}

object Test{
def scalaStatic = "scala static"
}
Scala is very happy about it, our Test class has a companion object. But for some reason Java breaks!
src/test1/java/JavaTest.java:5: cannot find symbol
symbol : method scalaStatic()
location: class test1.Test
public String usingScalaStatic() {return Test.scalaStatic();}
But we didn't change the object, only added the class. As we learned at the previous post, the companion class messes things a bit.
javap bin/test1/Test$ bin/test1/Test 
Compiled from "Test.scala"
public final class test1.Test$ extends java.lang.Object implements scala.ScalaObject{
public static final test1.Test$ MODULE$;
public static {};
public test1.Test$();
public java.lang.String scalaStatic();
public int $tag() throws java.rmi.RemoteException;
}

Compiled from "Test.scala"
public class test1.Test extends java.lang.Object implements scala.ScalaObject{
public test1.Test();
public void hi();
public int $tag() throws java.rmi.RemoteException;
}
so now we no longer have a static scalaStatic method. Scala code does not care about it, but it does matter when we want to integrate with Java. A solution could be something like
package test1.java;
import test1.Test$;
public class JavaTest{
public String usingScalaStatic() {return (new Test$()).scalaStatic();}
}
But its ugly and even worse, we create another instance of the object Test which is supposed to be a singleton! I wonder why the constructor is not private. Another, maybe better but not less ugly is
public class JavaTest{
public String usingScalaStatic() {return Test$.MODULE$.scalaStatic();}
}
Which works as well since MODULE$ is the member that keeps the static reference to the singleton. Needless to say, this is a nasty side effect. Hopefully it will fix at Scala 2.8 with the main bug.

3 comments:

Ismael Juma May 2, 2009 at 12:33 AM  

Hi Eishay,

The non-private constructor issue has been fixed, see:

https://lampsvn.epfl.ch/trac/scala/changeset/17463

About static forwarders for companion objects (apart from main), I added a link to the relevant trac issue in your previous blog entry.

Best,
Ismael

Eishay Smith May 2, 2009 at 8:56 AM  

Thanks Ismael, I assume the fix will be at 2.8. Its going to be a very interesting release :-)

Ismael Juma May 2, 2009 at 10:39 AM  

Yes, it should be. 2.8 is looking great indeed and I am really looking forward to it.

Best,
Ismael

Creative Commons License This work by Eishay Smith is licensed under a Creative Commons Attribution 3.0 Unported License.