1

我遇到了与 Scala generics 和 boxing的 Java 互操作性问题相同的问题,但我认为那里的解决方案对我不起作用,因为它需要修改第三方代码。

具体来说,从 Java(比如 MyJavaClass)我试图扩展一个本身扩展com.twitter.app.App的 Scala 类(MyScalaClass) 。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 时,我明白了

[错误] MyJavaClass.java:[11,8] MyJavaClass 不是抽象的,并且不会覆盖 com.twitter.util.Awaitable 中的抽象方法 result(com.twitter.util.Duration,com.twitter.util.Awaitable.CanAwait)

我想我只是出于某种原因必须实现result,所以我在 MyJavaClass 中实现了:

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

现在我明白了

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

将我的方法更改为

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

结果是

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

到底是什么......所以我开始用谷歌搜索。Java interoperability woes with Scala generics and boxing的答案似乎是说 Scala 在我的情况下生成与 Java 不兼容的字节码,如果我可以控制一些 Twitter 类(我认为CloseAwaitably)来泛化,我可以解决这个问题它与原始方法实现一起[U <: Unit]return ().asInstanceOf[U]在原始方法实现中欺骗 scalac 承认“某些不完全单位类型”的可能性(尽管我并不完全清楚它是如何工作的)。

我无法控制 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 类不必实现该方法,并且构建得很好。

我怎样才能解决这个问题?感谢参与。

4

1 回答 1

0

您可以使用类似的技巧来使 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。)

现在,使用 extends-unit 技巧,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 认可的泛型 Object vs something-else,javac 将找不到正确的方法。

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

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

爪哇:

package test;

public interface I {
  public void doThing();
}

然后是斯卡拉:

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
}

然后再次Java:

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!
于 2015-07-21T19:29:14.997 回答