Java互操作性与Scala泛型和装箱一样困难

Tra*_*own 8 java generics boxing scala

假设我有这个Scala特征:

trait UnitThingy {
  def x(): Unit
}
Run Code Online (Sandbox Code Playgroud)

提供Java实现很容易:

import scala.runtime.BoxedUnit;

public class JUnitThingy implements UnitThingy {
  public void x() {
    return;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在让我们从一般特征开始:

trait Foo[A] {
  def x(): A
}

trait Bar extends Foo[Unit]
Run Code Online (Sandbox Code Playgroud)

上面的方法不起作用,因为单位x返回现在已装箱,但解决方法很简单:

import scala.runtime.BoxedUnit;

public class JBar implements Bar {
  public BoxedUnit x() {
    return BoxedUnit.UNIT;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在假设我已经x在Scala端定义了一个实现:

trait Baz extends Foo[Unit] {
  def x(): Unit = ()
}
Run Code Online (Sandbox Code Playgroud)

我知道我不能x从Java 看到这个,所以我定义了自己的:

import scala.runtime.BoxedUnit;

public class JBaz implements Baz {
  public BoxedUnit x() {
    return BoxedUnit.UNIT;
  }
}
Run Code Online (Sandbox Code Playgroud)

但是这种情况爆发了:

[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
Run Code Online (Sandbox Code Playgroud)

如果我尝试抽象类 - 委托 - 超 - 特质技巧:

abstract class Qux extends Baz {
  override def x() = super.x()
}
Run Code Online (Sandbox Code Playgroud)

然后:

public class JQux extends Qux {}
Run Code Online (Sandbox Code Playgroud)

更糟糕的是:

[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]        ^
Run Code Online (Sandbox Code Playgroud)

(请注意,JQux如果Baz没有扩展,这个定义会正常工作Foo[Unit].)

如果你看看javap有关的内容Qux,那就太奇怪了:

public abstract class Qux implements Baz {
  public void x();
  public java.lang.Object x();
  public Qux();
}
Run Code Online (Sandbox Code Playgroud)

我认为这里的问题既BazQux必须scalac错误,但有一种解决方法吗?我真的不关心这个Baz部分,但是我有什么办法可以继承QuxJava吗?

Rex*_*err 8

它们不是scalac bug; 这就是Scala编译器正在努力代表您解决过程和方法之间的差异,而Java编译器却没有.

为了提高效率和Java兼容性,Unit非泛型返回的方法实际上是作为过程实现的(即返回类型是void).然后通过调用void版本并返回来实现泛型实现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
Run Code Online (Sandbox Code Playgroud)

问题是虽然javac会为特定的和泛型的Object返回类型做同样的事情,但它不理解Object- void交叉.

这是一个解释.有一种解决方法,但它使Scala层次结构复杂化:

trait Bazz[U <: Unit] extends Bar[Unit] {
  def x() = ().asInstanceOf[U]    // Must go here, not in Baz!
}
trait Baz extends Bazz[Unit] {}
Run Code Online (Sandbox Code Playgroud)

现在你已经强迫Scala考虑一些不完全Unit返回类型的可能性,所以它保留BoxedUnit了返回; 并Baz抛弃了这种可能性,但它并没有产生新void x()的混淆Java.

至少可以说,这很脆弱.但是,修复它可能对Java和Scala团队来说都是一项工作:只要版本存在,Java 就不开心BoxedUnit; 它受到void版本的积极骚扰.(你可以通过从Foo继承两次来生成一个抽象类;因为它不起作用,细节是不重要的.)Scala可能能够通过发出改进的字节码来单独完成它,该字节码在Java期望的地方有一个额外的BoxedUnit方法. ..不确定.

  • +1,这真的很聪明,而且我从未实际使用过. (2认同)