Suppose I've got this Scala trait:
trait UnitThingy {
def x(): Unit
}
Providing a Java implementation is easy enough:
import scala.runtime.BoxedUnit;
public class JUnitThingy implements UnitThingy {
public void x() {
return;
}
}
Now let's start with a generic trait:
trait Foo[A] {
def x(): A
}
trait Bar extends Foo[Unit]
The approach above won't work, since the unit x
returns is now boxed, but the workaround is easy enough:
import scala.runtime.BoxedUnit;
public class JBar implements Bar {
public BoxedUnit x() {
return BoxedUnit.UNIT;
}
}
Now suppose I've got an implementation with x
defined on the Scala side:
trait Baz extends Foo[Unit] {
def x(): Unit = ()
}
I know I can't see this x
from Java, so I define my own:
import scala.runtime.BoxedUnit;
public class JBaz implements Baz {
public BoxedUnit x() {
return BoxedUnit.UNIT;
}
}
But that blows up:
[error] .../JBaz.java:3: error: JBaz is not abstract and does not override abstract method x() in Baz
[error] public class JBaz implements Baz {
[error] ^
[error] /home/travis/tmp/so/js/newsutff/JBaz.java:4: error: x() in JBaz cannot implement x() in Baz
[error] public BoxedUnit x() {
[error] ^
[error] return type BoxedUnit is not compatible with void
And if I try the abstract-class-that-delegates-to-super-trait trick:
abstract class Qux extends Baz {
override def x() = super.x()
}
And then:
public class JQux extends Qux {}
It's even worse:
[error] /home/travis/tmp/so/js/newsutff/JQux.java:1: error: JQux is not abstract and does not override abstract method x() in Foo
[error] public class JQux extends Qux {}
[error] ^
(Note that this definition of JQux
would work just fine if Baz
didn't extend Foo[Unit]
.)
If you look at what javap
says about Qux
, it's just weird:
public abstract class Qux implements Baz {
public void x();
public java.lang.Object x();
public Qux();
}
I think the problems here with both Baz
and Qux
have to be scalac bugs, but is there a workaround? I don't really care about the Baz
part, but is there any way I can inherit from Qux
in Java?
They aren't scalac bugs; it's that the Scala compiler is working hard on your behalf to paper over the difference between procedures and methods, and the Java compiler is not.
For efficiency and Java compatibility, methods that return
Unit
non-generically are actually implemented as procedures (i.e. return type isvoid
). Then the generic implementation is implemented by calling thevoid
version and returningBoxedUnit
.The problem is that while javac will do the same thing for you with specific vs. generic
Object
-derived return types, it doesn't understand theObject
-void
crossover.That's an explanation. There is a workaround, though it complicates the Scala hierarchy:
Now you have forced Scala to consider the possibility of some not-exactly-
Unit
return type, so it keepsBoxedUnit
for the return; andBaz
throws away that possibility, but it doesn't generate a newvoid x()
to confuse Java.This is fragile, to say the least. Fixing it may be a job for both the Java and Scala teams, though: Java is not happy as long as the
BoxedUnit
version is there; it gets actively annoyed by thevoid
version. (You can generate an abstract class with both by inheriting from Foo twice; since it doesn't work the details are unimportant.) Scala might be able to do it alone by emitting altered bytecode that has an extra BoxedUnit method everywhere Java expects it...not sure.