Почему сопутствующий объект 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, очевидно, немного отличается.