打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
学习Scala:从HelloWorld开始


前言


最近在学习Scala语言,虽然还没有完全学通, 但是隐约可以体会到Scala的简洁和强大。 它既能让程序员使用函数式编程, 也提供了全面的面向对象编程。 在刚刚开始读《Scala编程》的时候, 刚读了几页, 我就被Scala语言吸引住了, 所以就一直读下去。 在学习的过程中, 也会有一些感悟, 对于一些原理, 也会尽量搞明白。 所以打算一边学习, 一边写博客, 虽然目前还没有深入, 但是还是有很多东西值得写下来。


我们知道, Scala也是一种运行于Java虚拟机上的语言, 既然能够运行于虚拟机之上, 那么它必然可以编译成class文件, 因为虚拟机只认class文件。 所以, scalac编译器将.scala源文件, 编译成class文件, 然后这些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
  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
  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
  1. scalac HelloWorld.scala  

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



其中有一个是和HelloWorld.scala对应的HelloWorld.class 。 那么HelloWorld$.class是什么呢?难道一个scala类可以生成多个class吗? 下面通过反编译来找到答案。

首先反编译HelloWorld.class : 

[plain] view plain copy
  1. javap -c -v -classpath . HelloWorld  

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

  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.          7: return  
  46. }  

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

1 getstatic访问一个静态字段, 这个静态字段是定义在HelloWorld$类中的MODULE$字段, 这个字段的类型是HelloWorld$ 。 讲到这里, 大概出现了单例的影子。 我们并没有定义这个类, 所以这个类是scala编译器自动生成的, 用来辅佐HelloWorld类。

2 然后使用这个静态对象调用main方法, 这个main方法是HelloWorld$类中的, 而不是当前HelloWorld中的。 它不是静态的, 而是成员方法。 


下面反编译HelloWorld$类: 
  1. javap -c -v -classpath . HelloWorld$  

反编译结果如下:
  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.          0: new           #2                  // class HelloWorld$  
  59.          3: invokespecial #12                 // Method "<init>":()V  
  60.          6: return  
  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.          8: return  
  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 5: 0  
  76. }  

从输出结果可以知道:


 HelloWorld$类有一个静态字段
  1. public static final HelloWorld$ MODULE$;  
  2.   flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL  

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

 
 HelloWorld$类还有一个静态初始化方法:
  1. public static {};  
  2.     flags: ACC_PUBLIC, ACC_STATIC  
  3.     Code:  
  4.       stack=1, locals=0, args_size=0  
  5.          0: new           #2                  // class HelloWorld$  
  6.          3: invokespecial #12                 // Method "<init>":()V  
  7.          6: return  

在这个静态初始化方法中, 使用new指令创建了一个HelloWorld$对象, 并且调用该对象的构造方法<init>初始化这个对象。 
实际上就是对静态字段MODULE$ 的赋值。

 HelloWorld$类还有一个main方法:
  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.         8: return  
  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 5: 0  

这个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的语法):

  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的语法):

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





本文基于分析class字节码来分析scala, 对class文件格式不熟悉的同学, 可以参考我的专栏: 深入理解Java语言 。




本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Matrix - 与 Java 共舞 - JVM之class文件结构
学习Scala:伴生对象的实现原理
ASM字节码编程 | 如果你只写CRUD,那这种技术栈你永远碰不到!!!
Java注解(Annotation)原理详解
第九章
JVM内幕:Java虚拟机详解 – 码农网
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服