学习Scala:孤立对象的实现原理

转载 2016年05月31日 14:50:29


在关于Scala的第一篇文章 学习Scala:从HelloWorld开始 中, 我们讲述了Scala的HelloWorld程序的执行原理。在Scala中,程序的入口使用孤立对象来实现, 在这篇博客中, 我们讲述了孤立对象是如何实现程序入口的, 不管Scala和Java的语法差别多大, 只要能以一定的方式实现标准的class文件入口类和入口函数, 就能被JVM执行。感兴趣的读者可以移步这篇博客。 

在《Scala编程》这本书中, 把孤立对象和伴生对象都叫做单例对象。孤立对象指的是只有一个使用object关键字定义的对象, 伴生对象是指有一个使用object关键字定义的对象, 除此之外还有一个使用class关键字定义的同名类, 这个同名的类叫做伴生类。在Scala中单例对象这个概念多少都会让人迷惑, 按《Scala编程》这本书中的说法, 使用object关键字修饰的对象就叫做单例对象。其实这里的单例和设计模式中的单例模式的概念并不尽相同。在Scala中没有静态的概念, 所有的东西都是面向对象的。其实object单例对象只是对静态的一种封装而已, 在class文件层面中,object单例对象就是用静态(static)来实现的。在本博客中会对这个概念进行详细讲述。除此之外,在本文中还会涉及一重要的概念, 叫做虚构类(synthetic class) 。

关于孤立对象的实现原理, 学习Scala:从HelloWorld开始 这篇文章 已经讲解的差不多了, 本博客可以看做对上一篇博客的补充。 



我们还是以简单的例子开始:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. object  Test {  
  2.     val a = "a string";  
  3.     def printString = println(a)  
  4. }  
在这个孤立对象上, 定义了一个属性和一个方法。 在编译完成之后, 会看到有两个class文件


也就是说, 这个孤立对象也被编译成一个同名类Test 。 除此之外, 还有一个叫做Test$的类, 这个以$结尾的类就是所谓的虚构类(synthetic class, 《Scala编程》中将之翻译为虚构类) 。

下面使用javap反编译Test.class , 得到如下结果(去掉了常量池等信息):

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Test  
  2.   SourceFile: "Test.scala"  
  3.   RuntimeVisibleAnnotations:  
  4.     0: #6(#7=s#8)  
  5.     ScalaSig: length = 0x3  
  6.      05 00 00  
  7.   minor version: 0  
  8.   major version: 50  
  9.   flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER  
  10.   
  11. {  
  12.   public static void printString();  
  13.     flags: ACC_PUBLIC, ACC_STATIC  
  14.     Code:  
  15.       stack=1, locals=0, args_size=0  
  16.          0: getstatic     #16                 // Field Test$.MODULE$:LTest$;  
  17.          3: invokevirtual #18                 // Method Test$.printString:()V  
  18.          6return  
  19.   
  20.   
  21.   public static java.lang.String a();  
  22.     flags: ACC_PUBLIC, ACC_STATIC  
  23.     Code:  
  24.       stack=1, locals=0, args_size=0  
  25.          0: getstatic     #16                 // Field Test$.MODULE$:LTest$;  
  26.          3: invokevirtual #22                 // Method Test$.a:()Ljava/lang/String;  
  27.          6: areturn  
  28. }  

由反编译的结果可以看出, 源码中的属性a对应一个静态的同名方法a(), 源码中的方法printString也对应一个静态的同名方法printString()。 静态方法a()调用Test$类中的静态字段MODULE$的a方法。 静态方法printString()调用Test$类中的静态字段MODULE$的printString方法。 如果用java来描述的话, Test类的逻辑是这样的:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Test{  
  2.   
  3.   
  4.     public static java.lang.String a(){  
  5.         return Test$.MODULE$.a()  
  6.     }  
  7.       
  8.     public static void printString(){  
  9.         Test$.MODULE$.printString()  
  10.     }  
  11. }  



下面再看Test类的虚构类Test$的javap反编译结果:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Test$  
  2.   SourceFile: "Test.scala"  
  3.     Scala: length = 0x0  
  4.   
  5.   
  6.   minor version: 0  
  7.   major version: 50  
  8.   flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER  
  9.   
  10. {  
  11.   public static final Test$ MODULE$;  
  12.     flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL  
  13.   
  14.   private final java.lang.String a;  
  15.     flags: ACC_PRIVATE, ACC_FINAL  
  16.   
  17.   public static {};  
  18.     flags: ACC_PUBLIC, ACC_STATIC  
  19.     Code:  
  20.       stack=1, locals=0, args_size=0  
  21.          0new           #2                  // class Test$  
  22.          3: invokespecial #12                 // Method "<init>":()V  
  23.          6return  
  24.   
  25.   public java.lang.String a();  
  26.     flags: ACC_PUBLIC  
  27.     Code:  
  28.       stack=1, locals=1, args_size=1  
  29.          0: aload_0  
  30.          1: getfield      #17                 // Field a:Ljava/lang/String;  
  31.          4: areturn  
  32.   
  33.   public void printString();  
  34.     flags: ACC_PUBLIC  
  35.     Code:  
  36.       stack=2, locals=1, args_size=1  
  37.          0: getstatic     #24                 // Field scala/Predef$.MODULE$:Lscala/Predef$;  
  38.          3: aload_0  
  39.          4: invokevirtual #26                 // Method a:()Ljava/lang/String;  
  40.          7: invokevirtual #30                 // Method scala/Predef$.println:(Ljava/lang/Object;)V  
  41.         10return  
  42.   
  43.   private Test$();  
  44.     flags: ACC_PRIVATE  
  45.     Code:  
  46.       stack=2, locals=1, args_size=1  
  47.          0: aload_0  
  48.          1: invokespecial #31                 // Method java/lang/Object."<init>":()V  
  49.          4: aload_0  
  50.          5: putstatic     #33                 // Field MODULE$:LTest$;  
  51.          8: aload_0  
  52.          9: ldc           #35                 // String a string  
  53.         11: putfield      #17                 // Field a:Ljava/lang/String;  
  54.         14return  
  55.   
  56. }  


首先看一下这个类里的内容。 


首先, 该类中有一个常量字段MODULE$, 它的类型就是当前的虚构类Test$ 。
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static final Test$ MODULE$;  
  2.   flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL  

编译器在Test$中默认添加了静态初始化方法, 用于对静态字段MODULE$初始化:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static {};  
  2.   flags: ACC_PUBLIC, ACC_STATIC  
  3.   Code:  
  4.     stack=1, locals=0, args_size=0  
  5.        0new           #2                  // class Test$  
  6.        3: invokespecial #12                 // Method "<init>":()V  
  7.        6return  


源码中的字段a在Test$中对应一个非静态的字段a , 由于源码中的a是val的, 所以在Test$中对应的a字段是final的
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private final java.lang.String a;  
  2.   flags: ACC_PRIVATE, ACC_FINAL  

在Test$中还有一个成员方法a()与字段a对应, 这个方法的逻辑是返回a的值
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public java.lang.String a();  
  2.   flags: ACC_PUBLIC  
  3.   Code:  
  4.     stack=1, locals=1, args_size=1  
  5.        0: aload_0  
  6.        1: getfield      #17                 // Field a:Ljava/lang/String;  
  7.        4: areturn  


源码中的方法printString对应Test$中的printString方法。 这个方法的逻辑是调用方法a()获取字段a的值, 并打印a的值。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void printString();  
  2.   flags: ACC_PUBLIC  
  3.   Code:  
  4.     stack=2, locals=1, args_size=1  
  5.        0: getstatic     #24                 // Field scala/Predef$.MODULE$:Lscala/Predef$;  
  6.        3: aload_0  
  7.        4: invokevirtual #26                 // Method a:()Ljava/lang/String;  
  8.        7: invokevirtual #30                 // Method scala/Predef$.println:(Ljava/lang/Object;)V  
  9.       10return  


此外, 编译器在Test$中还加入默认的构造方法, 不过这个构造方法是私有的。 无法为外部调用。如下:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private Test$();  
  2.   flags: ACC_PRIVATE  
  3.   Code:  
  4.     stack=2, locals=1, args_size=1  
  5.        0: aload_0  
  6.        1: invokespecial #31                 // Method java/lang/Object."<init>":()V  
  7.        4: aload_0  
  8.        5: putstatic     #33                 // Field MODULE$:LTest$;  
  9.        8: aload_0  
  10.        9: ldc           #35                 // String a string  
  11.       11: putfield      #17                 // Field a:Ljava/lang/String;  
  12.       14return  


如果用java代码描述的话,Test$的逻辑是这样的:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Test${  
  2.     public static final Test$ MODULE$ = new Test$();  
  3.   
  4.     private final String a = "a string";  
  5.   
  6.     public String a(){  
  7.         return a;  
  8.     }  
  9.   
  10.     public void printString(){  
  11.         println(a());  
  12.     }  
  13.   
  14.     private Test$(){}  
  15. }  


由此可见, 这个虚构类Test$是单例的 一方面, 这个类是编译器默认生成的,在Scala代码中无法访问到。 另一方面, Test$构造器私有了, 只在内部创建了一个对象赋给了静态引用MODULE$ 。 

所以, 在Scala里面称用object关键字修饰的对象是单例对象, 在实现的角度上看, 并不是十分确切。 虽然称之为对象, 但是编译器确实为他生成了一个类, 如上面例子中的object Test , 编译器确实生成了类Test。 但是这个类中只有静态方法, 即使是一个Scala中的字段, 也对应一个静态方法, 如上例中的字段a 。 这个类中的静态方法会访问虚构类Test$中的静态成员Test$ MODULE$ ,使用这个对象可以调用Test$中的其他成员方法,Test$中的成员和源码中的成员相对应, 只是会为源码中的字段添加同名方法。 主要的处理逻辑实际上是在虚构类Test$中完成的, Test类只是作为一个入口




下面是看一下Scala是如何实现对单例对象的调用的。 首先写一个Scala的入口类:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. object Main {  
  2.   //scala main   
  3.   def main(args : Array[String]){  
  4.     Test.printString  
  5.   }  
  6. }  

相同的原理, 入口类Main也是单例对象, 实现原理和Test是相同的。 大部分的逻辑都在虚构类Main$中的成员方法main中实现的。反编译 Main$后的结果如下:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Main$  
  2.   SourceFile: "Main.scala"  
  3.     Scala: length = 0x0  
  4.   minor version: 0  
  5.   major version: 50  
  6.   flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER  
  7.   
  8. {  
  9.   public static final Main$ MODULE$;  
  10.     flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL  
  11.   
  12.   public static {};  
  13.     flags: ACC_PUBLIC, ACC_STATIC  
  14.     Code:  
  15.       stack=1, locals=0, args_size=0  
  16.          0new           #2                  // class Main$  
  17.          3: invokespecial #12                 // Method "<init>":()V  
  18.          6return  
  19.   
  20.   public void main(java.lang.String[]);  
  21.     flags: ACC_PUBLIC  
  22.     Code:  
  23.       stack=1, locals=2, args_size=2  
  24.          0: getstatic     #19                 // Field Test$.MODULE$:LTest$;  
  25.          3: invokevirtual #22                 // Method Test$.printString:()V  
  26.          6return  
  27.   
  28.   private Main$();  
  29.     flags: ACC_PRIVATE  
  30.     Code:  
  31.       stack=1, locals=1, args_size=1  
  32.          0: aload_0  
  33.          1: invokespecial #26                 // Method java/lang/Object."<init>":()V  
  34.          4: aload_0  
  35.          5: putstatic     #28                 // Field MODULE$:LMain$;  
  36.          8return  
  37. }  


用Java代码实现如下:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Main${  
  2.     public static final Main$ MODULE$ = new Main$();  
  3.     public void main(String[] args){  
  4.         Test$.MODULE$.printString();  
  5.     }  
  6.   
  7.      private Main$(){}  
  8. }  


由此可见, 在Main$中的成员方法main中, 直接调用了Test$.MODULE$.printString()方法, 而绕过了Test类, 这也是合理的, 因为只有Test$才处理相关逻辑。


而Main.class用java代码表示如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Main{  
  2.     public static void main(String[] args){  
  3.         Main$.MODULE$.main(args);  
  4.     }  
  5. }  



做一下总结:


Main.class提供JVM的入口函数, 在入口函数中调用Main$的成员方法main, 而Main$的成员方法main又调用了Test$的成员方法printString来处理相关逻辑, 即打印字符串。 


单例对象的调用方式如下图所示:



在关于Scala的第一篇文章 学习Scala:从HelloWorld开始 中, 我们讲述了Scala的HelloWorld程序的执行原理。在Scala中,程序的入口使用孤立对象来实现, 在这篇博客中, 我们讲述了孤立对象是如何实现程序入口的, 不管Scala和Java的语法差别多大, 只要能以一定的方式实现标准的class文件入口类和入口函数, 就能被JVM执行。感兴趣的读者可以移步这篇博客。 

在《Scala编程》这本书中, 把孤立对象和伴生对象都叫做单例对象。孤立对象指的是只有一个使用object关键字定义的对象, 伴生对象是指有一个使用object关键字定义的对象, 除此之外还有一个使用class关键字定义的同名类, 这个同名的类叫做伴生类。在Scala中单例对象这个概念多少都会让人迷惑, 按《Scala编程》这本书中的说法, 使用object关键字修饰的对象就叫做单例对象。其实这里的单例和设计模式中的单例模式的概念并不尽相同。在Scala中没有静态的概念, 所有的东西都是面向对象的。其实object单例对象只是对静态的一种封装而已, 在class文件层面中,object单例对象就是用静态(static)来实现的。在本博客中会对这个概念进行详细讲述。除此之外,在本文中还会涉及一重要的概念, 叫做虚构类(synthetic class) 。

关于孤立对象的实现原理, 学习Scala:从HelloWorld开始 这篇文章 已经讲解的差不多了, 本博客可以看做对上一篇博客的补充。 



我们还是以简单的例子开始:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. object  Test {  
  2.     val a = "a string";  
  3.     def printString = println(a)  
  4. }  
在这个孤立对象上, 定义了一个属性和一个方法。 在编译完成之后, 会看到有两个class文件


也就是说, 这个孤立对象也被编译成一个同名类Test 。 除此之外, 还有一个叫做Test$的类, 这个以$结尾的类就是所谓的虚构类(synthetic class, 《Scala编程》中将之翻译为虚构类) 。

下面使用javap反编译Test.class , 得到如下结果(去掉了常量池等信息):

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Test  
  2.   SourceFile: "Test.scala"  
  3.   RuntimeVisibleAnnotations:  
  4.     0: #6(#7=s#8)  
  5.     ScalaSig: length = 0x3  
  6.      05 00 00  
  7.   minor version: 0  
  8.   major version: 50  
  9.   flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER  
  10.   
  11. {  
  12.   public static void printString();  
  13.     flags: ACC_PUBLIC, ACC_STATIC  
  14.     Code:  
  15.       stack=1, locals=0, args_size=0  
  16.          0: getstatic     #16                 // Field Test$.MODULE$:LTest$;  
  17.          3: invokevirtual #18                 // Method Test$.printString:()V  
  18.          6return  
  19.   
  20.   
  21.   public static java.lang.String a();  
  22.     flags: ACC_PUBLIC, ACC_STATIC  
  23.     Code:  
  24.       stack=1, locals=0, args_size=0  
  25.          0: getstatic     #16                 // Field Test$.MODULE$:LTest$;  
  26.          3: invokevirtual #22                 // Method Test$.a:()Ljava/lang/String;  
  27.          6: areturn  
  28. }  

由反编译的结果可以看出, 源码中的属性a对应一个静态的同名方法a(), 源码中的方法printString也对应一个静态的同名方法printString()。 静态方法a()调用Test$类中的静态字段MODULE$的a方法。 静态方法printString()调用Test$类中的静态字段MODULE$的printString方法。 如果用java来描述的话, Test类的逻辑是这样的:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Test{  
  2.   
  3.   
  4.     public static java.lang.String a(){  
  5.         return Test$.MODULE$.a()  
  6.     }  
  7.       
  8.     public static void printString(){  
  9.         Test$.MODULE$.printString()  
  10.     }  
  11. }  



下面再看Test类的虚构类Test$的javap反编译结果:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Test$  
  2.   SourceFile: "Test.scala"  
  3.     Scala: length = 0x0  
  4.   
  5.   
  6.   minor version: 0  
  7.   major version: 50  
  8.   flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER  
  9.   
  10. {  
  11.   public static final Test$ MODULE$;  
  12.     flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL  
  13.   
  14.   private final java.lang.String a;  
  15.     flags: ACC_PRIVATE, ACC_FINAL  
  16.   
  17.   public static {};  
  18.     flags: ACC_PUBLIC, ACC_STATIC  
  19.     Code:  
  20.       stack=1, locals=0, args_size=0  
  21.          0new           #2                  // class Test$  
  22.          3: invokespecial #12                 // Method "<init>":()V  
  23.          6return  
  24.   
  25.   public java.lang.String a();  
  26.     flags: ACC_PUBLIC  
  27.     Code:  
  28.       stack=1, locals=1, args_size=1  
  29.          0: aload_0  
  30.          1: getfield      #17                 // Field a:Ljava/lang/String;  
  31.          4: areturn  
  32.   
  33.   public void printString();  
  34.     flags: ACC_PUBLIC  
  35.     Code:  
  36.       stack=2, locals=1, args_size=1  
  37.          0: getstatic     #24                 // Field scala/Predef$.MODULE$:Lscala/Predef$;  
  38.          3: aload_0  
  39.          4: invokevirtual #26                 // Method a:()Ljava/lang/String;  
  40.          7: invokevirtual #30                 // Method scala/Predef$.println:(Ljava/lang/Object;)V  
  41.         10return  
  42.   
  43.   private Test$();  
  44.     flags: ACC_PRIVATE  
  45.     Code:  
  46.       stack=2, locals=1, args_size=1  
  47.          0: aload_0  
  48.          1: invokespecial #31                 // Method java/lang/Object."<init>":()V  
  49.          4: aload_0  
  50.          5: putstatic     #33                 // Field MODULE$:LTest$;  
  51.          8: aload_0  
  52.          9: ldc           #35                 // String a string  
  53.         11: putfield      #17                 // Field a:Ljava/lang/String;  
  54.         14return  
  55.   
  56. }  


首先看一下这个类里的内容。 


首先, 该类中有一个常量字段MODULE$, 它的类型就是当前的虚构类Test$ 。
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static final Test$ MODULE$;  
  2.   flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL  

编译器在Test$中默认添加了静态初始化方法, 用于对静态字段MODULE$初始化:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static {};  
  2.   flags: ACC_PUBLIC, ACC_STATIC  
  3.   Code:  
  4.     stack=1, locals=0, args_size=0  
  5.        0new           #2                  // class Test$  
  6.        3: invokespecial #12                 // Method "<init>":()V  
  7.        6return  


源码中的字段a在Test$中对应一个非静态的字段a , 由于源码中的a是val的, 所以在Test$中对应的a字段是final的
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private final java.lang.String a;  
  2.   flags: ACC_PRIVATE, ACC_FINAL  

在Test$中还有一个成员方法a()与字段a对应, 这个方法的逻辑是返回a的值
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public java.lang.String a();  
  2.   flags: ACC_PUBLIC  
  3.   Code:  
  4.     stack=1, locals=1, args_size=1  
  5.        0: aload_0  
  6.        1: getfield      #17                 // Field a:Ljava/lang/String;  
  7.        4: areturn  


源码中的方法printString对应Test$中的printString方法。 这个方法的逻辑是调用方法a()获取字段a的值, 并打印a的值。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void printString();  
  2.   flags: ACC_PUBLIC  
  3.   Code:  
  4.     stack=2, locals=1, args_size=1  
  5.        0: getstatic     #24                 // Field scala/Predef$.MODULE$:Lscala/Predef$;  
  6.        3: aload_0  
  7.        4: invokevirtual #26                 // Method a:()Ljava/lang/String;  
  8.        7: invokevirtual #30                 // Method scala/Predef$.println:(Ljava/lang/Object;)V  
  9.       10return  


此外, 编译器在Test$中还加入默认的构造方法, 不过这个构造方法是私有的。 无法为外部调用。如下:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private Test$();  
  2.   flags: ACC_PRIVATE  
  3.   Code:  
  4.     stack=2, locals=1, args_size=1  
  5.        0: aload_0  
  6.        1: invokespecial #31                 // Method java/lang/Object."<init>":()V  
  7.        4: aload_0  
  8.        5: putstatic     #33                 // Field MODULE$:LTest$;  
  9.        8: aload_0  
  10.        9: ldc           #35                 // String a string  
  11.       11: putfield      #17                 // Field a:Ljava/lang/String;  
  12.       14return  


如果用java代码描述的话,Test$的逻辑是这样的:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Test${  
  2.     public static final Test$ MODULE$ = new Test$();  
  3.   
  4.     private final String a = "a string";  
  5.   
  6.     public String a(){  
  7.         return a;  
  8.     }  
  9.   
  10.     public void printString(){  
  11.         println(a());  
  12.     }  
  13.   
  14.     private Test$(){}  
  15. }  


由此可见, 这个虚构类Test$是单例的 一方面, 这个类是编译器默认生成的,在Scala代码中无法访问到。 另一方面, Test$构造器私有了, 只在内部创建了一个对象赋给了静态引用MODULE$ 。 

所以, 在Scala里面称用object关键字修饰的对象是单例对象, 在实现的角度上看, 并不是十分确切。 虽然称之为对象, 但是编译器确实为他生成了一个类, 如上面例子中的object Test , 编译器确实生成了类Test。 但是这个类中只有静态方法, 即使是一个Scala中的字段, 也对应一个静态方法, 如上例中的字段a 。 这个类中的静态方法会访问虚构类Test$中的静态成员Test$ MODULE$ ,使用这个对象可以调用Test$中的其他成员方法,Test$中的成员和源码中的成员相对应, 只是会为源码中的字段添加同名方法。 主要的处理逻辑实际上是在虚构类Test$中完成的, Test类只是作为一个入口




下面是看一下Scala是如何实现对单例对象的调用的。 首先写一个Scala的入口类:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. object Main {  
  2.   //scala main   
  3.   def main(args : Array[String]){  
  4.     Test.printString  
  5.   }  
  6. }  

相同的原理, 入口类Main也是单例对象, 实现原理和Test是相同的。 大部分的逻辑都在虚构类Main$中的成员方法main中实现的。反编译 Main$后的结果如下:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Main$  
  2.   SourceFile: "Main.scala"  
  3.     Scala: length = 0x0  
  4.   minor version: 0  
  5.   major version: 50  
  6.   flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER  
  7.   
  8. {  
  9.   public static final Main$ MODULE$;  
  10.     flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL  
  11.   
  12.   public static {};  
  13.     flags: ACC_PUBLIC, ACC_STATIC  
  14.     Code:  
  15.       stack=1, locals=0, args_size=0  
  16.          0new           #2                  // class Main$  
  17.          3: invokespecial #12                 // Method "<init>":()V  
  18.          6return  
  19.   
  20.   public void main(java.lang.String[]);  
  21.     flags: ACC_PUBLIC  
  22.     Code:  
  23.       stack=1, locals=2, args_size=2  
  24.          0: getstatic     #19                 // Field Test$.MODULE$:LTest$;  
  25.          3: invokevirtual #22                 // Method Test$.printString:()V  
  26.          6return  
  27.   
  28.   private Main$();  
  29.     flags: ACC_PRIVATE  
  30.     Code:  
  31.       stack=1, locals=1, args_size=1  
  32.          0: aload_0  
  33.          1: invokespecial #26                 // Method java/lang/Object."<init>":()V  
  34.          4: aload_0  
  35.          5: putstatic     #28                 // Field MODULE$:LMain$;  
  36.          8return  
  37. }  


用Java代码实现如下:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Main${  
  2.     public static final Main$ MODULE$ = new Main$();  
  3.     public void main(String[] args){  
  4.         Test$.MODULE$.printString();  
  5.     }  
  6.   
  7.      private Main$(){}  
  8. }  


由此可见, 在Main$中的成员方法main中, 直接调用了Test$.MODULE$.printString()方法, 而绕过了Test类, 这也是合理的, 因为只有Test$才处理相关逻辑。


而Main.class用java代码表示如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class Main{  
  2.     public static void main(String[] args){  
  3.         Main$.MODULE$.main(args);  
  4.     }  
  5. }  



做一下总结:


Main.class提供JVM的入口函数, 在入口函数中调用Main$的成员方法main, 而Main$的成员方法main又调用了Test$的成员方法printString来处理相关逻辑, 即打印字符串。 


单例对象的调用方式如下图所示:



相关文章推荐

学习Scala:孤立对象的实现原理

在关于Scala的第一篇文章 学习Scala:从HelloWorld开始 中, 我们讲述了Scala的HelloWorld程序的执行原理。在Scala中,程序的入口使用孤立对象来实现, 在这篇博客中,...

scala中的孤立对象实现原理

《Scala编程》这本书中, 把孤立对象和伴生对象都叫做单例对象。孤立对象指的是只有一个使用object关键字定义的对象, 伴生对象是指有一个使用object关键字定义的对象, 除此之外还有一个使用c...

Scala学习笔记-伴生对象于孤立对象

Scala-伴生对象于孤立对象

学习Scala:伴生对象的实现原理

在上一篇关于Scala的文章 学习Scala:孤立对象的实现原理 中, 主要分析了孤立对象是如何实现的。 首先回顾一下。 孤立对象是只有一个object关键字修饰的对象。 该对象会编译成两个class...

SQL数据库恢复后出现对象名无效(SQL Server备份还原时造成孤立用户的解决方案

SQL数据库恢复后出现对象名无效(SQL Server备份还原时造成孤立用户的解决方案) 2011-04-18 09:38 以碰到这个烦人的问题,恢复的时候自带了个用户,但怎么...

2,HTK学习_基于HTK语音工具包进行孤立词识别的使用教程

参考连接: https://my.oschina.net/jamesju/blog/116151 http://blog.csdn.net/jojozhangju/article/details/...

SQL 数据库 学习 021 查询-04 in 的用法 --- 属于若干个孤立的值

我的电脑系统:Windows 10 64位 SQL Server 软件版本: SQL Server 2014 Express 本篇博客里面使用了 scott 库,如何你现在还没有添加这个库到你的服...

机器学习&数据挖掘笔记_13(用htk完成简单的孤立词识别)

最近在看图模型中著名的HMM算法,对应的一些理论公式也能看懂个大概,就是不太明白怎样在一个具体的机器学习问题(比如分类,回归)中使用HMM,特别是一些有关状态变量、观察变量和实际问题中变量的对应关系,...

数据挖掘学习—孤立点分析(异类分析)

孤立点是指数据集中那些小模式数据,它可能是度量或执行错误所导致的, 也可能是固有数据变异性的结果。Hawkins给出了其本质性定义: 孤立点是在数据集中与众不同的数据, 使人怀疑这些数据并非随机偏...
  • B_H_L
  • B_H_L
  • 2013年09月25日 09:46
  • 1243
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:学习Scala:孤立对象的实现原理
举报原因:
原因补充:

(最多只允许输入30个字)