Another benefit of the fact that Scala runs on the JVM is that I am able to use my favorite Java debugger, jswat. It's not perfect, and single-stepping through code often jumps around and takes a bit of getting used to, but overall it works surprisingly well given that JSwat was not designed for debugging Scala code.
After getting somewhat comfortable with writing a Scala-only application, I wanted to see how hard it would be to take an existing Java application and add some Scala code to it. I thought this would be a likely scenario for a Java shop that adopted Scala: rather than attempting to convert the Java codebase to Scala, keep the existing Java code and just do new coding in Scala. I had a Java application that I had written years ago, Mimprint (a photo printing app), that I had wanted to make some changes to for a while. I decided to make the changes in Scala and see how it went.
While it is trivial to write an all-Scala program that calls into existing Java libraries, adding Scala to an existing Java application is not quite so easy. The problem is that the Java and Scala compilation steps are separate: you can't compile both Java and Scala files in one go. If none of your Java files reference any Scala classes you can first compile all your Java classes, then compile your Scala classes. Or, if none of your Scala files reference any Java classes you can do it the other way around. But if you want your Java classes to have access to your Scala classes and also have Scala classes have access to your Java classes, that's a problem.
One solution is to manually figure out the dependency order and set up your build to alternate between Java and Scala, each time compiling the files that depend only on other files that have already been compiled. Ugh. And that doesn't work anyway if you have any circular dependencies.
I decided to take a slightly different approach, as suggested by a posting to the Scala mailing list. By using Java interfaces, as described in more detail below, I could take care of two problems at the same time.
Scala code can easily call directly into Java code, but sometimes calling Scala code from Java code is trickier, since the translation from Scala into bytecode is not quite as straightforward as for Java: sometimes the Scala compiler adds characters to symbols or makes other changes that must be explicitly handled when calling from Java. But a Scala class can implement a Java interface, and an instance of that class can be passed to a Java method expecting an instance of the interface. The Java class then calls the interface methods on that instance exactly as if it were a Java class instance.
This was the approach I used: I compiled the Java code first, with no references to any Scala code. I compiled the Scala code second, and it thus had full direct access to all of the Java code. In places where I wanted Java code to access Scala code, I defined a Java interface and had the Scala code implement that interface. The Java code was thus still defined without reference to the Scala code, but it could call the Scala code through the interface.
The above approach works fine for instance method calls, but a bit more work is needed for static method calls and for "new" calls. In order to make these work, I created an application-wide factory class that in turn had calls to the static methods and "new" calls. More precisely, I defined a Java interface with method calls for those capabilities. I then defined a Scala class that implemented that interface. As before, I could then pass an instance of that class to the Java classes that wanted to call the previously static methods and "new" calls. These calls became method calls on the instance of the factory interface I passed in.
This left the final problem of how I created the Scala instance of that factory interface. Given a Java main class, there was no straightforward way to call into Scala code to instantiate that factory class without having a compile-time reference to the Scala class. I probably could have tried loading the class dynamically from the Java main, but I decided the simpler solution was just to use a Scala main class, which instantiated my Scala factory class and then called into the Java main class. What I actually did with the Scala factory class was to pass it in to a static Java method that saved that pointer for use by a static get method in that class. That way any Java class could call that static get method, get the Scala factory instance of the Java factory interface, and call its methods.
To summarize, here are the steps I used in order to mix Java and Scala in one application:
- Java code does not directly call Scala code. Scala code can directly call Java code.
- In places where you want Java code to call Scala code, define a Java interface, use that in the Java code, define a Scala class that implements that interface.
- Define a factory interface in Java for static method calls and "new" calls from Java that you want to implement in Scala, and define a Scala class that implements that factory interface.
- Use a Scala main class that instantiates the Scala factory class, then calls into the Java main class.
This approach worked reasonably well while there was not much Scala code compared to Java code, but as the proportion of Scala code increased, the number of interfaces and the size of the factory interface increased, and it became bothersome. Eventually I decided to just bite the bullet and convert the remaining code over to Scala. If you happen to look at Mimprint you may notice that the current version is all Scala. If you are interested in seeing the mixed Java/Scala code and setup, download Mimprint version 0.2.0.
5 comments:
Wow, this is one of the few applications of the factory pattern that hasn't left me wondering why in the world it was actually needed. I wonder if this would work well with Groovy (there is a similar issue there).
Scala 2.7.2 (now in the release candidate stage) supports mixing Java and Scala much more freely, without needing to bother with all those interfaces.
You compile your mixed codebase in two stages: first you give all of your source files, both Scala and Java, to scalac. Then you give just the Java source files to javac.
It works in Eclipse, too.
I've been doing this succesfully with a mixed codebase of about 200K lines which is about 15% Scala (although that 15% used to be about 30% of the project when it was Java, since you can say the same thing in fewer lines in Scala).
@Seth: It has been on my TODO list for a while now to check out this new capability in 2.7.2 and update this post; now, with your comment, I don't have to!
Although fortunately the additional work described in this post will no longer be required when mixing Java and Scala, as Chris points out it may still be useful when trying to mix with other languages, such as Groovy or Jython.
From what I've seen, this is still useful if using Spring with the AOP interceptors as Spring doesn't seem to work with Scala (or Groovy for that matter) unless it has an interface it can put a proxy around.
Post a Comment