Lightning talk at the July 15th, 2010 Chicago-Area Scala Enthusiasts meeting.
Dean Wampler
This talk explores how Scala writes valid byte code and what that means for interoperation with Java. I compiled the code with sbt
and then used javap
to examine the byte code signatures.
See the following for more details:
- Programming Scala, Chapter 14:
- Java Interoperability
- Java Interoperability
- Daniel Spiewak's post on Interop Between Java and Scala
The notes that follow largely follow the outline of Daniel's blog post.
The code is built using sbt
. (The # foo
are comments and $
and >
are shell and sbt
prompts, respectively.
$ ./sbt # invoke the provided sbt script
> update # Upate jars
> compile # build the code.
(There are no tests provided.) The class files are written to the target/scala_2.8.0.RC7/classes/
.
Use javap
, part of the JDK distribution to see how the names, method signatures, etc. are encoded at the byte code level; how they appear to javac
or java
. Note the space before the package name interop
:
javap -classpath target/scala_2.8.0.RC7/classes/ interop.JListMapper
javap -classpath target/scala_2.8.0.RC7/classes/ 'interop.JListMapper$'
Note that you need to escape the '$' in the latter example. Otherwise, it is interpreted as the start of a shell variable expansion. I just put the object name in single quotes.
Use scalap
, part of the Scala distribution to see how the names, method signatures, etc. are reinterpreted as Scala.
scalap -classpath target/scala_2.8.0.RC7/classes/ interop.JListMapper scalap -classpath target/scala_2.8.0.RC7/classes/ 'interop.JListMapper$'
The signatures will look pretty much the same as the original source. Use the open-source tool jad to attempt to reverse engineer the byte code back to working Java. It doesn't always work completely. I'm not sure why, but it might make assumptions that javac
wrote the byte code and hence not properly understand idiosyncrasies of scalac
output.
There is one executable in the code.
scala -cp target/scala_2.8.0.RC7/classes/ interop.JFunctionMain foo bar baz
I'll discuss these examples in the following sections.
Scala allows a wider variety of characters for type and method names than Java's [_a-zA-Z][_a-zA-Z0-9]*
. So, Scala encodes the extra characters thusly:
trait AllOpChars {
def == : Unit // $eq$eq
def > : Unit // $greater
def < : Unit // $less
def + : Unit // $plus
def - : Unit // $minus
def * : Unit // $times
def / : Unit // $div
def \ : Unit // $bslash
def | : Unit // $bar
def ! : Unit // $bang
def ? : Unit // $qmark
def :: : Unit // $colon$colon
def % : Unit // $percent
def ^ : Unit // $up
def & : Unit // $amp
def @@ : Unit // $at$at
def ## : Unit // $hash$hash
def ~ : Unit // $tilde
}
There appear to be some additional coding conventions for "specialized" type instantiations, e.g., List[Double]
for List
. I couldn't find any documentation on these. Feedback welcome!
You can see an example of this encoding when you compile PersonTrait.scala
and run javap
on it. Look for the name_$eq
method. This is the compiler-generated setter method name_=
that allows you to write, e.g.,
myperson.name = "Bubba"
Classes declared in Scala vs. Java have almost identical byte code. The Scala generated code will implement a ScalaObject
interface.
Note that my book and Daniel's blog post refer to an internal $tag
method required by the ScalaObject
interface. This method was removed in Scala 2.8.
Traits with no defined methods or fields are identical to interfaces and can be used interchangeably between Java and Scala. For example, see the AbstractPerson
in PersonTrait.scala
.
Traits with methods lead to the generation of a companion Foo$class
class, which holds the method implementations. For example, see the Person
in PersonTrait.scala
. Two class files are generated by scalac
, Person.class
for the "interface portion" and a corresponding Person$class.class
file that contains the method bodies.
See how these are used in Scala and Java in the Employee.scala
and Employee.java
files, respectively.
Scala type parameters are a superset of Java generics, e.g., covariant and contravariant subtyping.
trait Function2[-A1, -A2, +R] {
def apply(a1: A1, a2: A2): R
}
Java only lets you specify covariant and contravariant behavior at the call site, not the definition site. Scala gets away with this and other type system enhancements because of type erasure! That maligned feature lets Scala "sneak in" the improved behavior.
When you write
val list = 1 :: 2 :: 3 :: Nil
You are actually just calling methods on List
.
abstract class List[+A] {
def ::[B >: A](e: B) = ...
...
}
Functions in Scala are objects, e.g., a one-argument function is the following.
trait Function1[-T, +R] {
def apply (t: T): R
def toString = ...
def compose[A](g: A => T): A => R = ...
def andThen[A](g: R => A): T => A = ...
}
You can use these in Java! However there's a catch. In 2.7.7, you had to define apply
and the internal method $tag
, something like this.
Function1<String,String> toUpper = new Function1<String,String>() { public String apply(String s) { return s.toUpperCase(); } public int $tag() { return 0; } };
In 2.8.0, $tag
is no longer required, but it also appears that the new @specialized
annotations cause problems when instantiating anonymous subclasses of the FunctionN
traits in Java code. You get errors for undefined andThen
methods, e.g., for type R
= Double
.
To work around this, create a concrete subclass of Function1[String,String]
in Scala code with a default implementation of the apply
method. Then, in the Java code, create a subclass that overrides apply
to do the actual work desired. An alternative is to have the default apply
call an abstract helper method, say doApply
, then define doApply
in your Java code subclass (i.e., use the Template Method pattern).
For an example, see JListMapper
and JFunctionMain
.
The most important issue you'll encounter, beyond what we've discussed so far, is the fact that some Java libraries, e.g., the Spring Framework and most IDEs, expect objects to follow JavaBeans conventions:
public class Person {
public Person(...) {...}
public String getName() {...};
public void setName(String s) {...};
// etc.
}
Often these tools use reflection to locate "bean properties" this way. In contrast, consider the corresponding Scala class.
class Person(var name: String, ...)
It uses name
for the getter and name_=
for the setter, of course. If you need bean methods, use the @scala.reflect.BeanProperty
on each field, or use @scala.reflect.BeanInfo
on the class to affect all fields.
See the PersonBean
example in PersonTrait.scala
and use javap
on the generated class files, PersonBean.class
and PersonBean$class.class
. As written, getName
and setName
methods are generated. Try this experiment, remove the assignment for name
, so that it is pure abstract. Compile again and look at the class files with javap
. You'll see that just the getter getName
is defined, not the setter setName
, even though we declared it as a var
. I don't know why...