scala中的孤立对象实现原理

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

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

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

[java] view
plain
 copy

  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

  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. public static void printString();
  12. flags: ACC_PUBLIC, ACC_STATIC
  13. Code:
  14. stack=1, locals=0, args_size=0
  15. 0: getstatic     #16                 // Field Test$.MODULE$:LTest$;
  16. 3: invokevirtual #18                 // Method Test$.printString:()V
  17. 6: return
  18. public static java.lang.String a();
  19. flags: ACC_PUBLIC, ACC_STATIC
  20. Code:
  21. stack=1, locals=0, args_size=0
  22. 0: getstatic     #16                 // Field Test$.MODULE$:LTest$;
  23. 3: invokevirtual #22                 // Method Test$.a:()Ljava/lang/String;
  24. 6: areturn
  25. }

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

[java] view
plain
 copy

  1. public final class Test{
  2. public static java.lang.String a(){
  3. return Test$.MODULE$.a()
  4. }
  5. public static void printString(){
  6. Test$.MODULE$.printString()
  7. }
  8. }

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

[java] view
plain
 copy

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

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

首先, 该类中有一个常量字段MODULE$, 它的类型就是当前的虚构类Test$ 。

[java] view
plain
 copy

  1. public static final Test$ MODULE$;
  2. flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

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

[java] view
plain
 copy

  1. public static {};
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=1, locals=0, args_size=0
  5. 0: new           #2                  // class Test$
  6. 3: invokespecial #12                 // Method "<init>":()V
  7. 6: return

源码中的字段a在Test$中对应一个非静态的字段a , 由于源码中的a是val的, 所以在Test$中对应的a字段是final的

[java] view
plain
 copy

  1. private final java.lang.String a;
  2. flags: ACC_PRIVATE, ACC_FINAL

在Test$中还有一个成员方法a()与字段a对应, 这个方法的逻辑是返回a的值

[java] view
plain
 copy

  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

  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. 10: return

此外, 编译器在Test$中还加入默认的构造方法, 不过这个构造方法是私有的。 无法为外部调用。如下:

[java] view
plain
 copy

  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. 14: return

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

[java] view
plain
 copy

  1. public final class Test${
  2. public static final Test$ MODULE$ = new Test$();
  3. private final String a = "a string";
  4. public String a(){
  5. return a;
  6. }
  7. public void printString(){
  8. println(a());
  9. }
  10. private Test$(){}
  11. }

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

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

下面是看一下Scala是如何实现对单例对象的调用的。 首先写一个Scala的入口类:

[java] view
plain
 copy

  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

  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. public static final Main$ MODULE$;
  9. flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
  10. public static {};
  11. flags: ACC_PUBLIC, ACC_STATIC
  12. Code:
  13. stack=1, locals=0, args_size=0
  14. 0: new           #2                  // class Main$
  15. 3: invokespecial #12                 // Method "<init>":()V
  16. 6: return
  17. public void main(java.lang.String[]);
  18. flags: ACC_PUBLIC
  19. Code:
  20. stack=1, locals=2, args_size=2
  21. 0: getstatic     #19                 // Field Test$.MODULE$:LTest$;
  22. 3: invokevirtual #22                 // Method Test$.printString:()V
  23. 6: return
  24. private Main$();
  25. flags: ACC_PRIVATE
  26. Code:
  27. stack=1, locals=1, args_size=1
  28. 0: aload_0
  29. 1: invokespecial #26                 // Method java/lang/Object."<init>":()V
  30. 4: aload_0
  31. 5: putstatic     #28                 // Field MODULE$:LMain$;
  32. 8: return
  33. }

用Java代码实现如下:

[java] view
plain
 copy

  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. private Main$(){}
  7. }

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

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

[java] view
plain
 copy

  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来处理相关逻辑, 即打印字符串。

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

时间: 2024-08-08 02:22:25

scala中的孤立对象实现原理的相关文章

scala中的伴生对象实现原理

孤立对象是只有一个object关键字修饰的对象. 该对象会编译成两个class文件, 一个是以孤立对象的名字命名的class,  一个是以孤立对象的名字后面加上一个$字符命名的class, 这个class又叫做虚构类. 源码中的孤立对象中的字段和方法, 都被编译成以孤立对象的名字命名的class中的静态方法, 这些静态方法都会访问单例的虚构类对象. 虚构了是传统意义上的单例模式, 并且在类初始化的时候有, 就会创建唯一的对象. 源码中的所有字段和方法都会在虚构类中有相对应的成员. 如果不明白的可

iOS中的僵尸对象的实现

僵尸对象对于我们调试程序来说很有用,在XCode中打开僵尸对象的方法是设置NSZombieEnabled环境变量为YES,这导致所有的对象都不会被释放,程序跑起来会时间长了内存占用量很大,这次我们就要来写一些代码,模仿XCode中的实现,这样我们也能够大体上了解了XCode中实现僵尸对象的原理了. Mike Ash在他的博客中已经解释了僵尸对象实现的细节,我在这里就算是翻译一下吧...  (这些链接可能需要翻墙...) OC中的对象都是结构体,结构体中第一个字段是一个isa,指向对象的类对象,类

好程序员大数据学习路线分享scala单列和伴生对象

scala单例 object SingletonDemo { ??def main(args: Array[String]): Unit = { ????val s = SessionFactory ????println(s.getSession) ????println(s.getSession.size) ?//.size得到几个session对象 ??} } object SessionFactory{ ??println("SessionFactory 被执行了") ?? ?

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

Scala-伴生对象于孤立对象 Scala虽然是基于JVM构建的,但与Java还是有很多区别,其实一个重要的区别是Scala比Java更面向对象(纯面向对象),最明显的用例是scala中所有的操作符都是方法(操作符面向的使用者是对象). 伴生对象/孤立对象也是scala作为纯面向对象语言的一种体现. 孤立对象 先看一个例子 object Test{ var a = "helloworld" def helloworld(): Unit = { println("hellowo

5.scala中的对象

排版乱?请移步原文获得更好的阅读体验 1.单例对象 scala中没有静态类或者静态方法,都是通过object实现的,它表示某个类的单例对象.如object People是class People的单例对象. 2.伴生对象 java中有些类可能会既有静态方法又有实例方法,在scala中静态方法存在于对象中,也就意味中scala中需要有这么一个东西,它既是类,也含有对象.其中的对象就是类的伴生对象,同样的,类就是对象的伴生类.需要注意的是类和它的伴生对象可以相互访问私有属性,它们也必须存在于同一个源

scala中List的泛型分析以及::类和Nil对象

学习了scala中List的泛型分析以及::类和Nil对象,List是对scala的类型系统的一个非常好的使用,进一步巩固List的知识对于理解scala的类型系统是非常有价值的.本讲主要分析了List的泛型.::类和Nil对象. List有两个非常重要的子类,一个是::,一个是Nil,这两个子类都是case class.Nil表示一个空的列表,而::表达的是一个非空的列表. 例子如下: case object Nil extends List[Nothing]{ override def is

scala中option、None、some对象

转载:http://www.jianshu.com/p/95896d06a94d 1.option类型避免对象是空值,造成空指针异常. 2.None对象表示null,在没有对象返回时使用,some在有对象值时使用. 避免null使用 大多数语言都有一个特殊的关键字或者对象来表示一个对象引用的是"无",在Java,它是null.在Java 里,null 是一个关键字,不是一个对象,所以对它调用任何方法都是非法的.但是这对语言设计者来说是一件令人疑惑的选择.为什么要在程序员希望返回一个对象

Java中HashSet存储对象判断是否重复原理分析

HashSet  根据每个对象的哈希码值(调用hashCode()获得)用固定的算法算出它的存储索引,把对象存放在一个叫散列表的相应位置(表元)中: 存对象时,hashSet集合首先调用该对象的hashCode方法来获得该对象的hashCode值,与hash表中的值进行比较.如果不存在,则直接把该对象存入集合中,并把该hashCode值存入hash表中,此次add操作结束.如果存在,则进行下面的计算. 通过"=="操作符判断已经存入的对象与要存入的对象是否为同一对象.如果true则集合

Scala学习回顾(五)---- scala中的apply

注:本文学习源自:DT大数据梦工厂(微信公众号:DT_Spark) 在Scala中,我们通常都会使用类似函数调用的语法.举例来说,如果s是一个字符串,那么s(i)就是该字符串的第i个字符.(而在java中,你会这样写:s.charAt(i).)在IDEA中运行如下代码: println("Hello(4)")//将打印出'o' 你可以把这种用法当做是()操作符的重载形式,它背后的实现原理是一个名为apply的方法.举例来说,在StringOps类的文档中,你会发现这样一个方法: def