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

Posted

技术标签:

【中文标题】涉及 BoxedUnit/void 返回类型的 Java-Scala 互操作问题【英文标题】:Java-Scala interop issue involving BoxedUnit/void return types 【发布时间】:2015-07-20 22:07:49 【问题描述】:

我遇到了与Java interoperability woes with Scala generics and boxing 相同的问题,但我认为那里的解决方案不适合我,因为它需要修改第三方代码。

具体来说,来自 Java(比如 MyJavaClass)我正在尝试扩展一个本身扩展 com.twitter.app.App 的 Scala 类 (MyScalaClass)。应用扩展了com.twitter.util.CloseAwaitably,而这又扩展了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 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,如果这是相关的:我真的不知道为什么它需要我实现 @首先是 987654333@。从 MyScalaClass 继承的 Scala 类不必实现该方法,并且构建得很好。

我该如何解决这个问题?感谢您的参与。

【问题讨论】:

难道你不能编写一个 Scala 包装类来转发你在 Java 类中提供的其他方法吗? 也许吧,但到目前为止我在这个方向上的努力都没有结果。尝试从 Scala 覆盖 result 总是会导致 error: method result overrides nothing. 我已经尝试了我能想到的所有签名损坏。此外,出于某种原因,从任何 Scala 子类实现 result 不是必要,这让我怀疑正在发生一些不正常的事情。 这看起来不是应该从子类中覆盖CloseAwaitably 中的result 吗? override def result(timeout: Duration)(permit: CanAwait): Unit = super.result(timeout)(permit); 显然没有。 如果我从开头删除override 并尝试重新编译,我会得到name *** between defined and inherited member...have same type after erasure 而不是method result overrides nothing 【参考方案1】:

您可以使用类似的技巧来使 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 中实现这些类。

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

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

然后是 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!

【讨论】:

你真的试过这个吗?我在 D 中得到了这个,method result overrides nothing。我已经尝试使用我的实际 MyScalaClass 实现,以及您的精简版本(和名称),错误是一样的。 trait D[Z1 <: unit extends awaitable override def result duration permit: canawait z1="().asInstanceOf[Z1]" e app with d public class j>J is not abstract and does not override abstract method result(com.twitter.util.Duration,com.twitter.util.Awaitable.CanAwait) in com.twitter.util.CloseAwaitably 这很有趣,因为 CloseAwaitably 是您方案中的 B,扩展 A... 你可以忽略我的第一条评论,因为我刚刚没有用implicit 标记permit。这样做之后,引用 CloseAwaitably 的第二条评论似乎是某种线索。我似乎已经从 Awaitable 中成功捕获了result,只是未能在 CloseAwaitably 中捕获 result @danielpcox - 你为什么要覆盖result?它没有在任何地方定义。在我的示例中,我没有覆盖 z result 不是在 CloseAwaitably 中定义的吗?如果我从上面删除override,我会得到[ERROR] method result in trait CloseAwaitably of type (timeout: com.twitter.util.Duration)(implicit permit: com.twitter.util.Awaitable.CanAwait)Unit and [ERROR] method result in trait D of type (timeout: com.twitter.util.Duration)(implicit permit: com.twitter.util.Awaitable.CanAwait)Unit [ERROR] (Note: this can be resolved by declaring an override in class E.)

以上是关于涉及 BoxedUnit/void 返回类型的 Java-Scala 互操作问题的主要内容,如果未能解决你的问题,请参考以下文章

没有任何返回类型的函数如何工作

序列类型数据的通用操作

C++ 函数形参里的const char作为返回值问题

vector作为函数返回类型

c++ cin返回值问题

涉及 Hibernate 返回的 List<Object[]> 的 Java 类转换异常