Почему объект Scala companion компилируется в два класса (оба Java and.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 6

3 ответа:

Важно понимать, что в scala 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.правило()). Таким образом, программа java может обращаться к методам синглетного объекта как к обычному статическому методу. Теперь вы можете спросить, почему 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();
  }  
}
Я считаю, что главная причина, по которой это не может быть так просто, заключается в том, что в СПМ вы не можете иметь в одном и том же классе метод экземпляра и статический метод с одной и той же сигнатурой. Впрочем, могут быть и другие причины.

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

Эта запись в блоге "a Look at How Scala Compiles to Java" должна ответить на ваш вопрос

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