有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Scala泛型和装箱带来的Java互操作性问题

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

如果我尝试一个抽象类,它委托给super trait技巧:

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

然后:

public class JQux extends Qux {}

更糟糕的是:

^{10}pr$

(注意,如果Baz没有扩展Foo[Unit],那么JQux的这个定义就可以很好地工作了。)

如果你看一下javapQux的看法,这很奇怪:

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

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


共 (1) 个答案

  1. # 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] {}
    

    现在,您强迫斯卡拉考虑一些不完全-^ {CD1>}返回类型的可能性,因此它保留了{{< CD4}}作为返回;而且Baz抛弃了这种可能性,但它不会生成一个新的void x()来混淆Java

    至少可以说,这是脆弱的。修复它可能是Java和Scala团队的一项工作,不过:只要有BoxedUnit版本,Java就不会高兴;它对void版本非常恼火。(通过从Foo继承两次,您可以用这两种方法生成一个抽象类;因为它不起作用,所以细节并不重要。)Scala可能可以通过发出经过修改的字节码来单独实现这一点,该字节码在Java期望的任何地方都有一个额外的BoxedUnit方法。。。不确定