Совместимость с 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...Точно сказать не могу.