Совместимость с Java сопряжена с дженериками и боксом Scala

Предположим, у меня есть эта черта 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;
  }
}

Теперь предположим, что у меня есть реализация с x определено на стороне Scala:

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

Я знаю, что не вижу этого x из Java, поэтому я определяю свой собственный:

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

(Обратите внимание, что это определение JQux будет работать просто отлично, если Baz не распространяется Foo[Unit].)

Если вы посмотрите на что javap говорит о QuxСтранно

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

Я думаю, что проблемы здесь с обоими Baz а также Qux должны быть ошибки скаляка, но есть ли обходной путь? Я действительно не забочусь о Baz часть, но есть ли способ унаследовать от Qux на яве?

1 ответ

Решение

Они не скалярные ошибки; дело в том, что компилятор 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: Java недовольна, пока BoxedUnit версия есть; это активно раздражается void версия. (Вы можете создать абстрактный класс с обоими, унаследовав от Foo дважды; поскольку он не работает, детали не важны.) Scala может сделать это самостоятельно, испуская измененный байт-код, имеющий дополнительный метод BoxedUnit везде, где его ожидает Java...Точно сказать не могу.

Другие вопросы по тегам