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$.