Skip to content

deanwampler/ScalaJavaInterop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Scala-Java Interoperability

Lightning talk at the July 15th, 2010 Chicago-Area Scala Enthusiasts meeting.

Dean Wampler

GitHub

Introduction

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.

References

See the following for more details:

The notes that follow largely follow the outline of Daniel's blog post.

Using the Examples

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.

Character Encoding

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

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 vs. Interfaces

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.

Generics

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.

"Operators" Are Methods

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) = ...
  ...
}

Higher-Order Functions in Java

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.

Interoperation with Java Libraries.

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...

About

A simple "lightning talk" about Scala-Java Interoperability for the Chicago Scala user group

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published