You're basically correct.
If you have the singleton
object Singleton {
def method = "Method result"
}
then compilation gives you
Singleton.class
Singleton$.class
and for the bytecode you find, first for Singleton
:
public final class Singleton extends java.lang.Object{
public static final java.lang.String method();
Signature: ()Ljava/lang/String;
Code:
0: getstatic #11; //Field Singleton$.MODULE$:LSingleton$;
3: invokevirtual #13; //Method Singleton$.method:()Ljava/lang/String;
6: areturn
}
that is, a public static method for each method of the class that references something called Singleton$.MODULE$
, and in Singleton$
:
public final class Singleton$ extends java.lang.Object implements scala.ScalaObject{
public static final Singleton$ MODULE$;
Signature: LSingleton$;
public static {};
Signature: ()V
Code:
0: new #9; //class Singleton$
3: invokespecial #12; //Method "<init>":()V
6: return
public java.lang.String method();
Signature: ()Ljava/lang/String;
Code:
0: ldc #16; //String Method result
2: areturn
private Singleton$();
Signature: ()V
Code:
0: aload_0
1: invokespecial #20; //Method java/lang/Object."<init>":()V
4: aload_0
5: putstatic #22; //Field MODULE$:LSingleton$;
8: return
}
Where you see that MODULE$
is what holds the instance of Singleton$
, and method
is just an ordinary method.
So, that's all there really is to it: create Singleton$
with a static field called MODULE$
to hold the unique instance of itself, populate that field, and then create a Singleton
with static methods that forward all static calls to the appropriate methods from Singleton$
.