scala编译的class字节码实现

时间:2020-12-05 17:09:44



我们知道, Scala也是一种运行于Java虚拟机上的语言, 既然能够运行于虚拟机之上, 那么它必然可以编译成class文件, 因为虚拟机只认class文件。 所以, scalac编译器将.scala源文件, 编译成class文件, 然后这些class文件被虚拟机加载并执行。

scala编译的class字节码实现


所以, 如果你对class文件格式和java虚拟机足够了解的话, 那么学习scala语言就会相对简单。Java文件编译成class文件, 而Scala源文件也是编译成class文件, 虽然他们语法大相径庭, 但是最后殊途同归。  如果我们能基于class文件分析scala的行为, 有助于理解scala语言。有人说scala的语法很多很难, 其实语法终归是写法, class文件的格式是不变的, 可以把scala的语法看成java语法的高级语法糖。


本系列博客基于分析class文件, 来分析scala的语法。 如果你对class文件格式不熟悉, 建议读一下我的专栏, 该专栏是专门分析class文件和JVM行为的。 专栏地址:


http://blog.csdn.net/column/details/zhangjg-java-blog.html



Scala的HelloWorld


按照IT界的传统, 下面我们就从HelloWorld开始分析。 下面是scala版的HelloWorld源码: [plain] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. object HelloWorld{  
  2.     def main(args : Array[String]){  
  3.         println("HelloWorld")  
  4.     }  
  5. }  

如果对scala的语法不是很熟悉, 并且对scala比较感兴趣, 建议先熟悉一下scala的基本语法。 这里简单说两以下几点:

1   以object关键字修饰一个类名, 这种语法叫做孤立对象,这个对象是单例的。 相当于将单例类和单例对象同时定义。
2   方法声明以def开头, 然后是方法名, 参数列表, 返回值, 等号, 方法体 。如下: [plain] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. def doSomeThing(x : Int) : Int = {  
  2.     x += 1  
  3. }  

 如果没有返回值, 可以省略等号, 直接写方法体。
3 Array[String]是scala的一种数据类型, 可以理解为字符串数组。
这篇博客的目的不是详细的讲解语法, 而是基于class文件来分析scala语法的实现方式, 所以对于语法只简单提一下 。 



反编译scala HelloWorld


我们所说的反编译, 是指使用javap工具反编译class文件, 所以, 在反编译之前, 要先使用scalac编译器编译该源文件:
[plain] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. scalac HelloWorld.scala  

命令执行完成后, 可以看到HelloWorld.scala所在的目录中多出两个class文件:

scala编译的class字节码实现

其中有一个是和HelloWorld.scala对应的HelloWorld.class 。 那么HelloWorld$.class是什么呢?难道一个scala类可以生成多个class吗? 下面通过反编译来找到答案。
首先反编译HelloWorld.class : 
[plain] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. javap -c -v -classpath . HelloWorld  

反编译结果如下: (为了便于讲述, 给出了所有的输出, 会有些长)

[java] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. Classfile /D:/Workspace/scala/scala-test/HelloWorld/HelloWorld.class  
  2.   Last modified 2014-4-1; size 586 bytes  
  3.   MD5 checksum 2ce2089f345445003ec6b4ef4ed4c6d1  
  4.   Compiled from "HelloWorld.scala"  
  5. public final class HelloWorld  
  6.   SourceFile: "HelloWorld.scala"  
  7.   RuntimeVisibleAnnotations:  
  8.     0: #6(#7=s#8)  
  9.     ScalaSig: length = 0x3  
  10.      05 00 00  
  11.   minor version: 0  
  12.   major version: 50  
  13.   flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER  
  14. Constant pool:  
  15.    #1 = Utf8               HelloWorld  
  16.    #2 = Class              #1             //  HelloWorld  
  17.    #3 = Utf8               java/lang/Object  
  18.    #4 = Class              #3             //  java/lang/Object  
  19.    #5 = Utf8               HelloWorld.scala  
  20.    #6 = Utf8               Lscala/reflect/ScalaSignature;  
  21.    #7 = Utf8               bytes  
  22.    #8 = Utf8               :Q!\t\t!S3mY><vN7ea ...  
  23.    #9 = Utf8               main  
  24.   #10 = Utf8               ([Ljava/lang/String;)V  
  25.   #11 = Utf8               HelloWorld$  
  26.   #12 = Class              #11            //  HelloWorld$  
  27.   #13 = Utf8               MODULE$  
  28.   #14 = Utf8               LHelloWorld$;  
  29.   #15 = NameAndType        #13:#14        //  MODULE$:LHelloWorld$;  
  30.   #16 = Fieldref           #12.#15        //  HelloWorld$.MODULE$:LHelloWorld$;  
  31.   #17 = NameAndType        #9:#10         //  main:([Ljava/lang/String;)V  
  32.   #18 = Methodref          #12.#17        //  HelloWorld$.main:([Ljava/lang/String;)V  
  33.   #19 = Utf8               Code  
  34.   #20 = Utf8               SourceFile  
  35.   #21 = Utf8               RuntimeVisibleAnnotations  
  36.   #22 = Utf8               ScalaSig  
  37. {  
  38.   public static void main(java.lang.String[]);  
  39.     flags: ACC_PUBLIC, ACC_STATIC  
  40.     Code:  
  41.       stack=2, locals=1, args_size=1  
  42.          0: getstatic     #16                 // Field HelloWorld$.MODULE$:LHelloWorld$;  
  43.          3: aload_0  
  44.          4: invokevirtual #18                 // Method HelloWorld$.main:([Ljava/lang/String;)V  
  45.          7return  
  46. }  

从输出结果可以看到, 这个类确实有传统意义上的main方法。 这个main方法中的字节码指令大概是这样:

1 getstatic访问一个静态字段, 这个静态字段是定义在HelloWorld$类中的MODULE$字段, 这个字段的类型是HelloWorld$ 。 讲到这里, 大概出现了单例的影子。 我们并没有定义这个类, 所以这个类是scala编译器自动生成的, 用来辅佐HelloWorld类。
2 然后使用这个静态对象调用main方法, 这个main方法是HelloWorld$类中的, 而不是当前HelloWorld中的。 它不是静态的, 而是成员方法。 

下面反编译HelloWorld$类:  [java] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. javap -c -v -classpath . HelloWorld$  

反编译结果如下:
[java] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. Classfile /D:/Workspace/scala/scala-test/HelloWorld/HelloWorld$.class  
  2.   Last modified 2014-4-1; size 596 bytes  
  3.   MD5 checksum 7b3e40952539579da28edc84f370ab9b  
  4.   Compiled from "HelloWorld.scala"  
  5. public final class HelloWorld$  
  6.   SourceFile: "HelloWorld.scala"  
  7.     Scala: length = 0x0  
  8.   
  9.   minor version: 0  
  10.   major version: 50  
  11.   flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER  
  12. Constant pool:  
  13.    #1 = Utf8               HelloWorld$  
  14.    #2 = Class              #1             //  HelloWorld$  
  15.    #3 = Utf8               java/lang/Object  
  16.    #4 = Class              #3             //  java/lang/Object  
  17.    #5 = Utf8               HelloWorld.scala  
  18.    #6 = Utf8               MODULE$  
  19.    #7 = Utf8               LHelloWorld$;  
  20.    #8 = Utf8               <clinit>  
  21.    #9 = Utf8               ()V  
  22.   #10 = Utf8               <init>  
  23.   #11 = NameAndType        #10:#9         //  "<init>":()V  
  24.   #12 = Methodref          #2.#11         //  HelloWorld$."<init>":()V  
  25.   #13 = Utf8               main  
  26.   #14 = Utf8               ([Ljava/lang/String;)V  
  27.   #15 = Utf8               scala/Predef$  
  28.   #16 = Class              #15            //  scala/Predef$  
  29.   #17 = Utf8               Lscala/Predef$;  
  30.   #18 = NameAndType        #6:#17         //  MODULE$:Lscala/Predef$;  
  31.   #19 = Fieldref           #16.#18        //  scala/Predef$.MODULE$:Lscala/Predef$;  
  32.   #20 = Utf8               HelloWorld  
  33.   #21 = String             #20            //  HelloWorld  
  34.   #22 = Utf8               println  
  35.   #23 = Utf8               (Ljava/lang/Object;)V  
  36.   #24 = NameAndType        #22:#23        //  println:(Ljava/lang/Object;)V  
  37.   #25 = Methodref          #16.#24        //  scala/Predef$.println:(Ljava/lang/Object;)V  
  38.   #26 = Utf8               this  
  39.   #27 = Utf8               args  
  40.   #28 = Utf8               [Ljava/lang/String;  
  41.   #29 = Methodref          #4.#11         //  java/lang/Object."<init>":()V  
  42.   #30 = NameAndType        #6:#7          //  MODULE$:LHelloWorld$;  
  43.   #31 = Fieldref           #2.#30         //  HelloWorld$.MODULE$:LHelloWorld$;  
  44.   #32 = Utf8               Code  
  45.   #33 = Utf8               LocalVariableTable  
  46.   #34 = Utf8               LineNumberTable  
  47.   #35 = Utf8               SourceFile  
  48.   #36 = Utf8               Scala  
  49. {  
  50.   public static final HelloWorld$ MODULE$;  
  51.     flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL  
  52.   
  53.   
  54.   public static {};  
  55.     flags: ACC_PUBLIC, ACC_STATIC  
  56.     Code:  
  57.       stack=1, locals=0, args_size=0  
  58.          0new           #2                  // class HelloWorld$  
  59.          3: invokespecial #12                 // Method "<init>":()V  
  60.          6return  
  61.   
  62.   public void main(java.lang.String[]);  
  63.     flags: ACC_PUBLIC  
  64.     Code:  
  65.       stack=2, locals=2, args_size=2  
  66.          0: getstatic     #19                 // Field scala/Predef$.MODULE$:Lscala/Predef$;  
  67.          3: ldc           #21                 // String HelloWorld  
  68.          5: invokevirtual #25                 // Method scala/Predef$.println:(Ljava/lang/Object;)V  
  69.          8return  
  70.       LocalVariableTable:  
  71.         Start  Length  Slot  Name   Signature  
  72.                0       9     0  this   LHelloWorld$;  
  73.                0       9     1  args   [Ljava/lang/String;  
  74.       LineNumberTable:  
  75.         line 50  
  76. }  

从输出结果可以知道:


 HelloWorld$类有一个静态字段 [java] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. public static final HelloWorld$ MODULE$;  
  2.   flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL  

它的访问修饰符是 public static final   , 类型是HelloWorld$ , 字段名是 MODULE$ 。

   HelloWorld$类还有一个静态初始化方法: [java] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. public static {};  
  2.     flags: ACC_PUBLIC, ACC_STATIC  
  3.     Code:  
  4.       stack=1, locals=0, args_size=0  
  5.          0new           #2                  // class HelloWorld$  
  6.          3: invokespecial #12                 // Method "<init>":()V  
  7.          6return  

在这个静态初始化方法中, 使用new指令创建了一个HelloWorld$对象, 并且调用该对象的构造方法<init>初始化这个对象。 
实际上就是对静态字段MODULE$ 的赋值。
 HelloWorld$类还有一个main方法:
[java] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. public void main(java.lang.String[]);  
  2.    flags: ACC_PUBLIC  
  3.    Code:  
  4.      stack=2, locals=2, args_size=2  
  5.         0: getstatic     #19                 // Field scala/Predef$.MODULE$:Lscala/Predef$;  
  6.         3: ldc           #21                 // String HelloWorld  
  7.         5: invokevirtual #25                 // Method scala/Predef$.println:(Ljava/lang/Object;)V  
  8.         8return  
  9.      LocalVariableTable:  
  10.        Start  Length  Slot  Name   Signature  
  11.               0       9     0  this   LHelloWorld$;  
  12.               0       9     1  args   [Ljava/lang/String;  
  13.      LineNumberTable:  
  14.        line 50  

这个main方法不是静态的, 是一个实例方法, 从它的字节码指令可以看出, 实现的是打印字符串HelloWorld的逻辑。


HelloWorld的实现方式总结


从上面的讲述中, 我们可知, scalac编译器使用两个class文件, 实现HelloWorld.scala源文件中的逻辑, 除了生成HelloWorld.class外, 还生产一个HelloWorld$.class 。实现逻辑如下:

1 传统意义上的入口main方法被编译在HelloWorld.class中
2  在HelloWorld.class中的main方法中, 会访问HelloWorld$.class中的静态字段MODULE$  (这个字段的类型就是HelloWorld$) , 并使用这个字段调用HelloWorld$中的main方法。
HelloWorld中的逻辑有点像下面这样(以下伪代码旨在说明原理, 并不符合java或scala的语法):
[java] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. public class HelloWorld{  
  2.       
  3.     public static void main(String[] args){  
  4.   
  5.   
  6.       HelloWorld$.MODULE$.main(args);  
  7.     }     
  8. }  

3 真正打印字符串“HelloWorld”的逻辑在HelloWorld$中。 这个类有一个main实例方法, 来处理打印字符串的逻辑, 并且该类中有一个HelloWorld$类型的静态字段MODULE$ 。 上面的HelloWorld类中的入口main方法, 正是通过这个字段调用的HelloWorld$的main实例方法来打印"HelloWorld" 。

HelloWorld$中的代码有点像这样(以下伪代码旨在说明原理, 并不符合java或scala的语法):

[java] view plain copy scala编译的class字节码实现scala编译的class字节码实现
  1. public final class HelloWorld${  
  2.   
  3.     public static final HelloWorld$ MODULE$ = new HelloWorld$();  
  4.   
  5.     public void main(String[] args){  
  6.         println("HelloWorld");  
  7.     }  
  8. }