【问题标题】:Java interoperability woes with Scala generics and boxingJava 与 Scala 泛型和装箱的互操作性问题
【发布时间】:2014-12-25 21:45:37
【问题描述】:

假设我有这个 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;
  }
}

现在假设我在 Scala 端定义了 x 的实现:

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

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

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

(请注意,如果Baz 没有扩展Foo[Unit]JQux 的这个定义就可以正常工作。)

如果你看看javapQux 的评价,你会觉得很奇怪:

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

我认为BazQux 的问题必须是scalac 错误,但有解决方法吗?我并不真正关心Baz 部分,但是有什么方法可以从Java 中的Qux 继承?

【问题讨论】:

    标签: java scala generics boxing


    【解决方案1】:

    它们不是 scalac 错误;是 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
    

    问题是,虽然 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 团队的工作:只要有 BoxedUnit 版本,Java 高兴;它被void 版本激怒了。 (您可以通过从 Foo 继承两次来生成一个包含两者的抽象类;因为它不起作用,所以细节并不重要。)Scala 可以通过发出改变的字节码来单独完成它,该字节码在 Java 期望的任何地方都有一个额外的 BoxedUnit 方法。 ..不确定。

    【讨论】:

    • 也许“bug”这个词是错误的——我的意思更像是“背叛了一个相当合理的期望”。 BoxedUnit.UNIT 技巧让我实现了一个通用的Unit 方法,那么为什么添加一层继承会破坏它呢?这并不像它以任何实际方式使该方法不那么通用。
    • 并不是只有一层继承,而是有一个新的public void x()方法,javac对此并不满意。它认为 that 是实现该特征的那个,即使周围有一个 BoxedUnit 版本。它正在寻找BoxedUnit x() 的签名,这是它自己会生成的。
    • (有一个BoxedUnit 版本,但我的意思是它的类型是Object。)
    • +1,这真的很聪明,而且我从来没有真正使用过。
    • 这是一个错误。 Groovy 没有这些问题,但可以使用 GPar 进行函数式编程和处理 actor 和所有内容,并且可以 100% 与 Java 互操作。
    猜你喜欢
    • 2014-01-21
    • 1970-01-01
    • 1970-01-01
    • 2013-07-01
    • 2011-02-17
    • 1970-01-01
    • 2011-10-26
    • 2012-04-11
    • 2013-04-11
    相关资源
    最近更新 更多