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;And a Scala class extending it
public class SuperTest{
protected String superMethod() {return "super";}
public static String superStatic() {return "super static";}
}
package test1Which gives us
import test1.java.SuperTest
class Test extends SuperTest{
def useSuper = println(superMethod)
def useSuperStatic = println(superStatic)
}
.../src/test1/Test.scala:7: error: not found: value superStaticNote that scalac didn't have problem with using superMethod, its only superStatic who had the problem. Adding this import would solve the problem
def useSuperStatic = println(superStatic)
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 test1gives us
object Test{
def scalaStatic = "scala static"
}
javap bin/test1/TestSo we can do the following from Java
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;
}
package test1.java;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
import test1.Test;
public class JavaTest{
public String usingScalaStatic() {return Test.scalaStatic();}
}
package test1Scala is very happy about it, our Test class has a companion object. But for some reason Java breaks!
class Test{
def hi = println("hi")
}
object Test{
def scalaStatic = "scala static"
}
src/test1/java/JavaTest.java:5: cannot find symbolBut we didn't change the object, only added the class. As we learned at the previous post, the companion class messes things a bit.
symbol : method scalaStatic()
location: class test1.Test
public String usingScalaStatic() {return Test.scalaStatic();}
javap bin/test1/Test$ bin/test1/Testso 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
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;
}
package test1.java;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
import test1.Test$;
public class JavaTest{
public String usingScalaStatic() {return (new Test$()).scalaStatic();}
}
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:
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
Thanks Ismael, I assume the fix will be at 2.8. Its going to be a very interesting release :-)
Yes, it should be. 2.8 is looking great indeed and I am really looking forward to it.
Best,
Ismael
Post a Comment