Java互操作性因Scala泛型和装箱而陷入困境

特拉维斯·布朗

假设我具有以下Scala特性:

trait UnitThingy {
  def x(): Unit
}

提供Java实现非常容易:

import scala.runtime.BoxedUnit;

public class JUnitThingy implements UnitThingy {
  public void x() {
    return;
  }
}

现在让我们从一个通用特征开始:

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

trait Bar extends Foo[Unit]

上面的方法行不通,因为x现在将返回单元装箱,但是解决方法非常简单:

import scala.runtime.BoxedUnit;

public class JBar implements Bar {
  public BoxedUnit x() {
    return BoxedUnit.UNIT;
  }
}

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

trait Baz extends Foo[Unit] {
  def x(): Unit = ()
}

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

import scala.runtime.BoxedUnit;

public class JBaz implements Baz {
  public BoxedUnit x() {
    return BoxedUnit.UNIT;
  }
}

但这炸了:

[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

如果我尝试将抽象类委托给超级特征,则:

abstract class Qux extends Baz {
  override def x() = super.x()
}

接着:

public class JQux extends Qux {}

更糟糕的是:

[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]        ^

(请注意,JQux如果Baz不进行扩展,则此定义将很好用Foo[Unit]。)

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

public abstract class Qux implements Baz {
  public void x();
  public java.lang.Object x();
  public Qux();
}

我这里都认为存在的问题BazQux必须scalac错误,但有一种解决方法吗?我并不真正在乎这一Baz部分,但是有什么方法可以Qux在Java中继承呢?

雷克斯·克尔

它们不是scalac bug。这是因为Scala编译器正在代表您努力工作,以阐明过程和方法之间的差异,而Java编译器则没有。

为了提高效率和Java兼容性,Unit非一般返回的方法实际上被实现为过程(即return type为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

问题是,尽管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] {}

现在,您已迫使Scala考虑某种非精确Unit返回类型的可能性,因此它会一直保持BoxedUnit返回状态;Baz抛弃了这种可能性,但它不会产生void x()使Java迷惑的新方法。

至少可以说这是脆弱的。修复它可能是在Java和Scala团队都工作,但:Java是幸福的,只要BoxedUnit版本是存在的; 它被void版本激怒了(您可以通过从Foo继承两次来生成两者的抽象类;因为它无法正常工作,细节并不重要。)Scala可以通过发出更改的字节码来单独完成它,该字节码在Java期望的任何地方都有额外的BoxedUnit方法。 ..没有把握。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章