Как компилятор Scala обрабатывает конкретные методы?

Если у меня есть следующий класс Scala:

abstract class MyOrdered extends Ordered[MyOrdered] {
    def id: Int
    def compare(that : MyOrdered) : Int =
        if (that==null) 1 else (id-that.id)
}

Тогда мне нужно только определить метод id в Scala, чтобы получить конкретный класс. Но если я попытаюсь расширить его на Java, компилятор говорит, что отсутствуют все конкретные методы Ordered. Значит ли это, что компилятор Scala выполняет только реализацию конкретных методов Ordered в конкретных классах Scala?

Это кажется очень расточительным, потому что я мог бы иметь десятки конкретных классов, реализующих MyOrdered, и все они получат копию того же кода, когда на самом деле этого достаточно, чтобы просто поместить его прямо в базовый класс MyOrdered. Кроме того, это очень затрудняет создание Java-дружественного API Scala. Есть ли способ заставить компилятор Scala поставить определения метода там, где он должен был это сделать, в любом случае, кроме создания конкретного класса с помощью реализации фиктивных методов?

Даже funnier объявляет конкретный метод final в признаке Scala. В этом случае он до сих пор не реализован в абстрактном классе Scala, который расширяет черту, но не может быть реализован в классе Java, который расширяет абстрактный класс Scala, поскольку он помечен как окончательный. Это определенно ошибка компилятора. Окончательные абстрактные методы не имеют смысла, даже если они являются законными в JVM, по-видимому.

Ответ 1

Scala 2.9.1.RC1

Позвольте представить вам наш друг :javap в REPL, который может быть полезен для диагностики ошибок. Сначала определим класс,

scala> abstract class MyOrdered extends Ordered[MyOrdered] {
     |     def id: Int
     |     def compare(that : MyOrdered) : Int =
     |         if (that==null) 1 else (id-that.id)
     | }
defined class MyOrdered

И затем попросите посмотреть байт-код JVM,

scala> :javap -v MyOrdered
Compiled from "<console>"
public abstract class MyOrdered extends java.lang.Object implements scala.math.Ordered,scala.ScalaObject

...
** I'm skipping lots of things here: $less, $lessEq, ... **
...

public boolean $greater(java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   invokestatic    #19; //Method scala/math/Ordered$class.$greater:(Lscala/math/Ordered;Ljava/lang/Object;)Z
   5:   ireturn
  LineNumberTable: 
   line 7: 0

...

public abstract int id();

public int compare(MyOrdered);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_1
   1:   ifnonnull   8
   4:   iconst_1
   5:   goto    17
   8:   aload_0
   9:   invokevirtual   #38; //Method id:()I
   12:  aload_1
   13:  invokevirtual   #38; //Method id:()I
   16:  isub
   17:  ireturn
  LineNumberTable: 
   line 10: 0

 ...

Мы видим, что scalac фактически генерирует методы в MyOrdered, соответствующие тем конкретным в признаке Ordered. Например, метод > переводится на $greater и в основном просто вызывает scala/math/Ordered$class.$greater. Если нам нравится, теперь мы можем найти байт-код для определений конкретных признаков,

scala> :javap -v scala.math.Ordered$class
Compiled from "Ordered.scala"
public abstract class scala.math.Ordered$class extends java.lang.Object
...
public static boolean $greater(scala.math.Ordered, java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   invokeinterface #12,  2; //InterfaceMethod scala/math/Ordered.compare:(Ljava/lang/Object;)I
   7:   iconst_0
   8:   if_icmple   15
   11:  iconst_1
   12:  goto    16
   15:  iconst_0
   16:  ireturn
  LineNumberTable: 
   line 46: 0
...

Наконец, давайте проверим вашу гипотезу о том, что подкласс M of MyOrdered получает полную копию всех методов

scala> class M extends MyOrdered { def id = 2 }
defined class M

scala> :javap -v M
Compiled from "<console>"
public class M extends MyOrdered implements scala.ScalaObject
....
** No extra methods besides id **
....

Нет, похоже, что здесь нет дублирования кода.

В заключение,

  • Scalac делает некоторую магию с чертами с конкретными методами, поэтому не пытайтесь наследовать их на Java. Абстрактные классы должны быть в порядке.

  • JVM не поддерживает имена символьных методов, Scala одиночные объекты, а также черты с конкретными методами, поэтому компилятору Scala необходимо выполнить некоторый перевод и использовать зарезервированный символ $.

Если у вас все еще возникают проблемы с Java-взаимодействием, надеюсь, :javap поможет вам в диагностике конкретной проблемы.

Ответ 2

Примечание: последняя фиксация для Scal 2.12.x (август 2016) может немного оптимизировать вещи в compiler/scala/tools/nsc/backend/jvm:

SD-192 Схема изменения для супер-аксессуаров класса

Вместо того, чтобы поместить код тела метода признаков в статический метод, оставьте это в методе по умолчанию.
Статический метод (необходимый как цель супервызов) теперь использует invokespecial для точного вызова этого метода.