Почему сопутствующий объект Scala компилируется в два класса (компиляторы Java и.NET)?

object ScalaTrueRing {
  def rule = println("To rule them all")
}

этот кусок кода будет скомпилирован в байт-код Java, если я его декомпилирую, то эквивалентный Java-код будет похож на этот:

public final class JavaTrueRing
{
  public static final void rule()
  {
    ScalaTrueRing..MODULE$.rule();
  }
}


/*    */ public final class JavaTrueRing$
/*    */   implements ScalaObject
/*    */ {
/*    */   public static final  MODULE$;
/*    */ 
/*    */   static
/*    */   {
/*    */     new ();
/*    */   }
/*    */ 
/*    */   public void rule()
/*    */   {
/* 11 */     Predef..MODULE$.println("To rule them all");
/*    */   }
/*    */ 
/*    */   private JavaTrueRing$()
/*    */   {
/* 10 */     MODULE$ = this;
/*    */   }
/*    */ }

он скомпилирован в два класса, и если я использую компилятор Scala.net, он будет скомпилирован в код MSIL, и эквивалентный код C# будет выглядеть следующим образом:

public sealed class ScalaTrueRing
{
    public static void rule()
    {
        ScalaTrueRing$.MODULE$.rule();
    }
}

[Symtab]
public sealed class ScalaTrueRing$ : ScalaObject
{
    public static ScalaTrueRing$ MODULE$;
    public override void rule()
    {
        Predef$.MODULE$.println("To rule them all");
    }
    private ScalaTrueRing$()
    {
        ScalaTrueRing$.MODULE$ = this;
    }
    static ScalaTrueRing$()
    {
        new ScalaTrueRing$();
    }
}

Это также скомпилировано в два класса.

Почему компиляторы Scala (один для Java и один для.NET) делают это? Почему он просто не вызывает метод println в методе статических правил?

3 ответа

Решение

Важно понимать, что в object на самом деле это гражданин первого класса: это фактический экземпляр, который можно передавать как любой другой объект. К примеру:

trait Greetings {
  def hello() { println("hello") }
  def bye() { println("bye") }
}

object FrenchGreetings extends Greetings {
  override def hello() { println("bonjour") }
  override def bye() { println("au revoir") }
}

def doSomething( greetings: Greetings ) {
  greetings.hello()
  println("... doing some work ...")
  greetings.bye()
}

doSomething( FrenchGreetings )

В отличие от статических методов, наш одноэлементный объект имеет полное полиморфное поведение. doSomething действительно будет называть наше переопределение hello а также bye методы, а не реализации по умолчанию:

bonjour
... doing some work ...
au revoir

Итак object реализация обязательно должна быть надлежащим классом. Но для совместимости с Java, компилятор также генерирует статические методы, которые просто перенаправляют на уникальный экземпляр (MODULE$) класса (см. JavaTrueRing.rule()). Таким образом, Java-программа может обращаться к методам объекта singleton как обычный статический метод. Теперь вы можете спросить, почему scala не помещает перенаправители статических методов в тот же класс, что и методы экземпляра. Это даст нам что-то вроде:

public final class JavaTrueRing implements ScalaObject {
  public static final  MODULE$;

  static {
    new JavaTrueRing();
  }

  public void rule() {
    Predef.MODULE$.println("To rule them all");
  }

  private JavaTrueRing() {
    MODULE$ = this;
  }

  // Forwarders
  public static final void rule() {
    MODULE$.rule();
  }  
}

Я полагаю, что главная причина, почему это не может быть так просто, заключается в том, что в JVM вы не можете иметь в одном классе метод экземпляра и статический метод с одинаковой сигнатурой. Могут быть и другие причины.

Перефразируя "Программирование в Scala" - потому что сопутствующий объект Scala (одноэлементный объект) - это больше, чем просто держатель статических методов. Будучи экземпляром другого Java-класса, он позволяет разработчику расширять одноэлементные объекты и особенности смешивания. Это невозможно сделать статическими методами.

Эта запись в блоге "Взгляд на компиляцию Scala в Java" должна ответить на ваш вопрос.

Обычно ClassName$.class являются результатами внутренних классов - Scala, очевидно, немного отличается.

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