涉及BoxedUnit/void返回类型的Java-Scala互操作问题



我遇到了与Scala泛型和装箱相同的Java互操作性问题,但我认为解决方案不适合我,因为它需要修改第三方代码。

具体来说,来自Java(说MyJavaClass)我试图扩展一个Scala类(MyScalaClass),它本身扩展了com.twitter.app.App。应用程序扩展com.twitter.util。closeawaitable,这又扩展了com.twitter.util.Awaitable。

Awaitable            // trait where `result` is defined
 ^-CloseAwaitably    // trait with some impl of `result`
   ^-App             // trait with no mention of `result`
     ^-MyScalaClass  // abstract class with no mention of `result`
       ^-MyJavaClass // just trying to get this guy to compile
                     // and it expects an impl of `result`

所有这些都是说,当我最终通过编写MyJavaClass扩展MyScalaClass时,我得到

[ERROR] MyJavaClass.java:[11,8] MyJavaClass不是抽象的不覆盖抽象方法结果(com.twitter.util.Duration com.twitter.util.Awaitable.CanAwait)在com.twitter.util.Awaitable

我想我只是要实现result出于某种原因,所以我这样做,在MyJavaClass:

public void result(com.twitter.util.Duration d,
                   com.twitter.util.Awaitable.CanAwait c)
{}

现在得到

[ERROR]返回类型void与scala.runtime.BoxedUnit不兼容

将我的方法改为

public BoxedUnit result(com.twitter.util.Duration d,
                        com.twitter.util.Awaitable.CanAwait c)
{return BoxedUnit.UNIT;}

搜索结果

[ERROR]返回类型scala.runtime.BoxedUnit与void不兼容

什么鬼…然后我开始用谷歌搜索。泛型Java互操作性问题的答案与Scala和拳击似乎说,Scala生成Java-incompatible字节码在我的情况下,我可以闲逛的问题如果我有控制的一些Twitter类(我认为 CloseAwaitably)与[U <: Unit] genericize和return ().asInstanceOf[U]原始方法实现欺骗那个承认"一些not-exactly-Unit类型"的可能性(尽管我不完全清楚这是如何工作的)。

我没有控制Twitter类,只有MyScalaClass和MyJavaClass。我实际上并不关心 result 方法 -我只是想能够从MyJavaClass扩展MyScalaClass,实现我在MyScalaClass中定义的一些抽象方法。为了它的价值,我正在使用Scala 2.10.4, JDK 1.8和(twitter) util-core_2.10 6.22.0,如果这是相关的:我真的不知道为什么它要求我首先实现result。从MyScalaClass继承的Scala类不需要实现这个方法,并且构建得很好。

我怎样才能绕过这个?

您可以使用类似的技巧使Scala的部分正确,但javac仍然会感到困惑,因为它正在寻找"错误"的东西,而不是那些正确的东西。(JVM会查找正确的内容,所以运行良好。)

血淋淋的细节如下。

Twitter的层次结构可以简化如下:

package test
trait A[+Z] { def z(): Z }
trait B extends A[Unit] { def z() = () }
trait C extends B {}

现在,当你写你的类时,你不只是扩展c。相反,你

package test
abstract class D[U <: Unit] extends A[U] with C {
  override def z: U = (this: B).z.asInstanceOf[U]
}
abstract class E extends D[Unit] {}

,其中E代替MyScalaClass。(zresult)

现在,使用扩展单元技巧,Java可能需要的所有签名都出现了:

$ javap test.B
public interface test.B extends test.A{
    public abstract void z();
}
$ javap test.D
public abstract class test.D extends java.lang.Object implements test.C{
    public scala.runtime.BoxedUnit z();
    public java.lang.Object z();
    public void z();
    public test.D();
}

void z不再抽象了!事实上,什么都不是。

但是javac 还是不高兴。

package test;
public class J extends E {
  public J() {}
}
$ javac -cp /jvm/scala-library.jar:. J.java
J.java:3: test.J is not abstract and does not override
          abstract method z() in test.B

嗯…Javac,对吗?

如果我们使J抽象,它解释了真正的问题是什么:

J.java:3: z() in test.D cannot implement z() in test.B; 
          attempting to use incompatible return type
found   : scala.runtime.BoxedUnit
required: void

也就是说,如果两个方法只在返回类型上不同,并且它不是java认可的泛型对象与其他对象,则javac将找不到正确的对象。

在我看来这更像是javac bug而不是scalac bug。不幸的是,它使您无法在Java中实现这些类。

作为一种变通方法,您可以(笨拙地)通过将两个类链接在一起来使用组合。

Java:

package test;
public interface I {
  public void doThing();
}

然后Scala:

package test
trait A[+Z] { def z(): Z }
trait B extends A[Unit] { def z(): Unit = println("Ok") }
trait C extends B {}
class D extends C {
  private var myI: I
  def i = myI
  def bind(newI: I) { myI = newI }
  def doJavaThing = i.doThing
}

package test;
public class J implements I {
  public static D d;
  public J(D myD) { d = myD; d.bind(this); }
  public void doThing() { System.out.println("Works!"); }
}

至少可以让你在需要的时候来回切换:

scala> new test.J(new test.D)
res0: test.J = test.J@18170f98
scala> res0.doThing
Works!
scala> res0.asScala.doJavaThing
Works!
scala> res0.asScala.asJava.doThing
Works!

最新更新