Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java interoperability woes with Scala generics and boxing

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?

like image 301
Travis Brown Avatar asked Oct 30 '14 00:10

Travis Brown


People also ask

Are Scala and Java interoperable?

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.

Is Scala fully compatible with Java?

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.


1 Answers

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.

like image 111
Rex Kerr Avatar answered Dec 28 '22 16:12

Rex Kerr