Проблема взаимодействия Java-Scala, связанная с возвращаемыми типами BoxedUnit/void

У меня та же проблема, что и в проблемах совместимости Java с обобщениями и боксом Scala, но я не думаю, что решение там будет работать для меня, потому что для этого потребуется модификация стороннего кода.

В частности, из Java (скажем, MyJavaClass) я пытаюсь расширить класс Scala (MyScalaClass), который сам расширяет com.twitter.app.App. Приложение расширяет com.twitter.util.CloseAwaitbly, а это, в свою очередь, расширяет 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`

Все это говорит о том, что когда я, наконец, доберусь до расширения MyScalaClass, написав MyJavaClass, я получу

[ОШИБКА] 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)
{}

Теперь я получаю

[ОШИБКА] возвращаемый тип 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 с обобщениями и боксом Scala, похоже, говорит о том, что Scala генерирует несовместимый с Java байт-код в моей ситуации, и что я мог бы решить эту проблему, если бы у меня был контроль над некоторыми классами Twitter (я думаю, CloseAwaitbly), чтобы генерировать это с [U <: Unit] а также return ().asInstanceOf[U] в оригинальной реализации метода, чтобы обмануть скаляк в допущении возможности "некоторого не совсем типа Unit" (хотя я не совсем понимаю, как это работает).

У меня нет контроля над классами Twitter, только MyScalaClass и MyJavaClass. Я на самом деле не забочусь о result метод - я просто хочу иметь возможность расширять MyScalaClass из MyJavaClass, реализуя некоторые абстрактные методы, которые я определил в MyScalaClass. Что бы это ни стоило, я использую Scala 2.10.4, JDK 1.8 и (twitter) util-core_2.10 6.22.0, и в случае, если это актуально: я действительно не знаю, почему это требует от меня реализации result на первом месте. Класс Scala, унаследованный от MyScalaClass, не обязательно должен реализовывать этот метод, и он прекрасно собирается.

Как я могу обойти это? Спасибо за игру.

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, (А также z является result.)

Теперь, с помощью трюка 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 больше не абстрактно! На самом деле ничего нет.

Но Джавак все еще не счастлив.

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

Хм... Джавак, да, это так?

И если мы сделаем 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, чем на скаляр. К сожалению, это не позволяет реализовать классы в 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!
Другие вопросы по тегам