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?
Of all the alternative JVM languages, Scala's interoperability with Java source code is among the most seamless. This chapter begins with a discussion of interoperability with code written in Java. Because Scala syntax is primarily a superset of Java syntax, invoking Java code from Scala is usually straightforward.
The Scala compiler allows you to build against both Java classes and Java source code. That way, if you have bidirectional dependency between Java and Scala, you can build them together without worrying about order.
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 is void
). Then the generic implementation is implemented by calling the void
version and returning BoxedUnit
.
public abstract class Qux implements Baz {
public void x();
Code:
0: aload_0
1: invokestatic #17 // Method Baz$class.x:(LBaz;)V
4: return
public java.lang.Object x();
Code:
0: aload_0
1: invokevirtual #22 // Method x:()V
4: getstatic #28 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
7: areturn
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 the Object
-void
crossover.
That's an explanation. There is a workaround, though it complicates the Scala hierarchy:
trait Bazz[U <: Unit] extends Bar[Unit] {
def x() = ().asInstanceOf[U] // Must go here, not in Baz!
}
trait Baz extends Bazz[Unit] {}
Now you have forced Scala to consider the possibility of some not-exactly-Unit
return type, so it keeps BoxedUnit
for the return; and Baz
throws away that possibility, but it doesn't generate a new void 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 the void
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With