最近在学习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语法的高级语法糖。

Scala的HelloWorld


按照IT界的传统, 下面我们就从HelloWorld开始分析。 下面是scala版的HelloWorld源码:

object HelloWorld{
def main(args : Array[String]){
println("HelloWorld")
}
}
  • 如果对scala的语法不是很熟悉, 并且对scala比较感兴趣, 建议先熟悉一下scala的基本语法。 这里简单说两以下几点:
    以object关键字修饰一个类名, 这种语法叫做孤立对象,这个对象是单例的。 相当于将单例类和单例对象同时定义。
  • 方法声明以def开头, 然后是方法名, 参数列表, 返回值, 等号, 方法体 。如下:
def doSomeThing(x : Int) : Int = {
x += 1
}
  • 如果没有返回值, 可以省略等号, 直接写方法体。
    Array[String]是scala的一种数据类型, 可以理解为字符串数组。
    这篇博客的目的不是详细的讲解语法, 而是基于class文件来分析scala语法的实现方式, 所以对于语法只简单提一下 。

反编译scala HelloWorld

我们所说的反编译, 是指使用javap工具反编译class文件, 所以, 在反编译之前, 要先使用scalac编译器编译该源文件:

scalac HelloWorld.scala 

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

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

首先反编译HelloWorld.class :

javap -c -v -classpath . HelloWorld  
 

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

Classfile /D:/Workspace/scala/scala-test/HelloWorld/HelloWorld.class
Last modified 2014-4-1; size 586 bytes
MD5 checksum 2ce2089f345445003ec6b4ef4ed4c6d1
Compiled from "HelloWorld.scala"
public final class HelloWorld
SourceFile: "HelloWorld.scala"
RuntimeVisibleAnnotations:
0: #6(#7=s#8)
ScalaSig: length = 0x3
05 00 00
minor version: 0
major version: 50
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
#1 = Utf8 HelloWorld
#2 = Class #1 // HelloWorld
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 HelloWorld.scala
#6 = Utf8 Lscala/reflect/ScalaSignature;
#7 = Utf8 bytes
#8 = Utf8 :Q!\t\t!S3mY><vN7ea ...
#9 = Utf8 main
#10 = Utf8 ([Ljava/lang/String;)V
#11 = Utf8 HelloWorld$
#12 = Class #11 // HelloWorld$
#13 = Utf8 MODULE$
#14 = Utf8 LHelloWorld$;
#15 = NameAndType #13:#14 // MODULE$:LHelloWorld$;
#16 = Fieldref #12.#15 // HelloWorld$.MODULE$:LHelloWorld$;
#17 = NameAndType #9:#10 // main:([Ljava/lang/String;)V
#18 = Methodref #12.#17 // HelloWorld$.main:([Ljava/lang/String;)V
#19 = Utf8 Code
#20 = Utf8 SourceFile
#21 = Utf8 RuntimeVisibleAnnotations
#22 = Utf8 ScalaSig
{
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #16 // Field HelloWorld$.MODULE$:LHelloWorld$;
3: aload_0
4: invokevirtual #18 // Method HelloWorld$.main:([Ljava/lang/String;)V
7: return
}

从输出结果可以看到, 这个类确实有传统意义上的main方法。

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

反编译结果如下:

Classfile /D:/Workspace/scala/scala-test/HelloWorld/HelloWorld$.class
Last modified 2014-4-1; size 596 bytes
MD5 checksum 7b3e40952539579da28edc84f370ab9b
Compiled from "HelloWorld.scala"
public final class HelloWorld$
SourceFile: "HelloWorld.scala"
Scala: length = 0x0 minor version: 0
major version: 50
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
#1 = Utf8 HelloWorld$
#2 = Class #1 // HelloWorld$
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 HelloWorld.scala
#6 = Utf8 MODULE$
#7 = Utf8 LHelloWorld$;
#8 = Utf8 <clinit>
#9 = Utf8 ()V
#10 = Utf8 <init>
#11 = NameAndType #10:#9 // "<init>":()V
#12 = Methodref #2.#11 // HelloWorld$."<init>":()V
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 scala/Predef$
#16 = Class #15 // scala/Predef$
#17 = Utf8 Lscala/Predef$;
#18 = NameAndType #6:#17 // MODULE$:Lscala/Predef$;
#19 = Fieldref #16.#18 // scala/Predef$.MODULE$:Lscala/Predef$;
#20 = Utf8 HelloWorld
#21 = String #20 // HelloWorld
#22 = Utf8 println
#23 = Utf8 (Ljava/lang/Object;)V
#24 = NameAndType #22:#23 // println:(Ljava/lang/Object;)V
#25 = Methodref #16.#24 // scala/Predef$.println:(Ljava/lang/Object;)V
#26 = Utf8 this
#27 = Utf8 args
#28 = Utf8 [Ljava/lang/String;
#29 = Methodref #4.#11 // java/lang/Object."<init>":()V
#30 = NameAndType #6:#7 // MODULE$:LHelloWorld$;
#31 = Fieldref #2.#30 // HelloWorld$.MODULE$:LHelloWorld$;
#32 = Utf8 Code
#33 = Utf8 LocalVariableTable
#34 = Utf8 LineNumberTable
#35 = Utf8 SourceFile
#36 = Utf8 Scala
{
public static final HelloWorld$ MODULE$;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL public static {};
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: new #2 // class HelloWorld$
3: invokespecial #12 // Method "<init>":()V
6: return public void main(java.lang.String[]);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: getstatic #19 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: ldc #21 // String HelloWorld
5: invokevirtual #25 // Method scala/Predef$.println:(Ljava/lang/Object;)V
8: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LHelloWorld$;
0 9 1 args [Ljava/lang/String;
LineNumberTable:
line 5: 0
}

从输出结果可以知道:
HelloWorld$类有一个静态字段

public static final HelloWorld$ MODULE$;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

它的访问修饰符是 public static final   , 类型是HelloWorld$ , 字段名是 MODULE$ 。
HelloWorld$类还有一个静态初始化方法:

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

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

public void main(java.lang.String[]);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: getstatic #19 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: ldc #21 // String HelloWorld
5: invokevirtual #25 // Method scala/Predef$.println:(Ljava/lang/Object;)V
8: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LHelloWorld$;
0 9 1 args [Ljava/lang/String;
LineNumberTable:
line 5: 0
  1. 这个main方法不是静态的, 是一个实例方法, 从它的字节码指令可以看出, 实现的是打印字符串HelloWorld的逻辑。
    HelloWorld的实现方式总结
    从上面的讲述中, 我们可知, scalac编译器使用两个class文件, 实现HelloWorld.scala源文件中的逻辑, 除了生成HelloWorld.class外, 还生产一个HelloWorld$.class 。实现逻辑如下:
  2. 传统意义上的入口main方法被编译在HelloWorld.class中
    在HelloWorld.class中的main方法中, 会访问HelloWorld$.class中的静态字段MODULE$  (这个字段的类型就是HelloWorld$) , 并使用这个字段调用HelloWorld$中的main方法。
    HelloWorld中的逻辑有点像下面这样(以下伪代码旨在说明原理, 并不符合java或scala的语法):
public class HelloWorld{  

    public static void main(String[] args){  

      HelloWorld$.MODULE$.main(args);
}
}

3.     真正打印字符串“HelloWorld”的逻辑在HelloWorld$中。 这个类有一个main实例方法, 来处理打印字符串的逻辑, 并且该类中有一个HelloWorld$类型的静态字段MODULE$ 。 上面的HelloWorld类中的入口main方法, 正是通过这个字段调用的HelloWorld$的main实例方法来打印"HelloWorld" 。
HelloWorld$中的代码有点像这样(以下伪代码旨在说明原理, 并不符合java或scala的语法):

public final class HelloWorld${  

    public static final HelloWorld$ MODULE$ = new HelloWorld$();  

    public void main(String[] args){
println("HelloWorld");
}
}
 
 
转自:http://blog.csdn.net/zhangjg_blog

Scala入门:从HelloWorld开始【源码及编译】的更多相关文章

  1. 使用 IntelliJ IDEA 导入 Spark 最新源码及编译 Spark 源代码

    前言   其实啊,无论你是初学者还是具备了有一定spark编程经验,都需要对spark源码足够重视起来. 本人,肺腑之己见,想要成为大数据的大牛和顶尖专家,多结合源码和操练编程. 准备工作 1.sca ...

  2. 使用 IntelliJ IDEA 导入 Spark 最新源码及编译 Spark 源代码(博主强烈推荐)

    前言   其实啊,无论你是初学者还是具备了有一定spark编程经验,都需要对spark源码足够重视起来. 本人,肺腑之己见,想要成为大数据的大牛和顶尖专家,多结合源码和操练编程. 准备工作 1.sca ...

  3. net-snmp源码VS2013编译添加加密支持(OpenSSL)

    net-snmp源码VS2013编译添加加密支持(OpenSSL) snmp v3 协议使用了基于用户的安全模型,具有认证和加密两个模块. 认证使用的算法是一般的消息摘要算法,例如MD5/SHA等.这 ...

  4. net-snmp源码VS2013编译添加加密支持(OpenSSL)(在VS里配置编译OpenSSL)

    net-snmp源码VS2013编译添加加密支持(OpenSSL) snmp v3 协议使用了基于用户的安全模型,具有认证和加密两个模块. 认证使用的算法是一般的消息摘要算法,例如MD5/SHA等.这 ...

  5. Hadoop2.x源码-编译剖析

    1.概述 最近,有小伙伴涉及到源码编译.然而,在编译期间也是遇到各种坑,在求助于搜索引擎,技术博客,也是难以解决自身所遇到的问题.笔者在被询问多次的情况下,今天打算为大家来写一篇文章来剖析下编译的细节 ...

  6. 【转】Android用NDK和整套源码下编译JNI的不同

    原文网址:http://www.devdiv.com/android_ndk_jni_-blog-99-2101.html 前些天要写个jni程序,因为才几行代码,想着用ndk开发可能容易些,就先研究 ...

  7. 【Android 系统开发】CyanogenMod 13.0 源码下载 编译 ROM 制作 ( 手机平台 : 小米4 | 编译平台 : Ubuntu 14.04 LTS 虚拟机)

                 分类: Android 系统开发(5)                                              作者同类文章X 版权声明:本文为博主原创文章 ...

  8. 从源码(编译)安装golang 二

    h1 { margin-top: 0.6cm; margin-bottom: 0.58cm; direction: ltr; color: #000000; line-height: 200%; te ...

  9. MySQL源码包编译安装

    +++++++++++++++++++++++++++++++++++++++++++标题:MySQL数据库实力部署时间:2019年3月9日内容:MySQL源码包进行编译,然后部署MySQL单实例重点 ...

  10. 通过清华大学镜像下载Android源码并编译源码

    之前看源码都是在Windows下用SourceInsight看,虽然达到了研究源码的效果,但终究还是有遗憾...趁着周末,准备在Ubuntu虚拟机上下载编译源码. 之前下源码时,有了解一些Androi ...

随机推荐

  1. linux下C语言实现的哈希链表【转】

    转自:http://blog.chinaunix.net/uid-28458801-id-4276934.html 操作系统:ubuntu10.04 前言:     在稍微大点的项目中,基本都会遇到算 ...

  2. 生成器(generator)和迭代(iterable , iterator, iteration)

    在搞清楚Generator之前,我们先讨论一下 iterable , iterator, iteration 1.Iterable 我们知道,在Python中所有东西都是object, 比如说变量,容 ...

  3. Java Web 远程调试

    Java Web 远程 调试 Tomcat 下载压缩版服务器 环境:Tomcat.Eclipse,做远程调试我们并不需要其他特殊插件 1.配置Tomcat/bin/startup.bat 在前面增加代 ...

  4. ftp--pureftpd1.0.46

    pureftpd的新版本1.0.46安装过程与之前的相同 但是之后的配置,有些许不同 pureftpd安装过程: # cd /usr/local/src # wget # cd pure-1.0.46 ...

  5. MySQL 和 Oracle 主键自增长

    1.MySQL 1)建表 auto_increment:每插入一条数据,客户表(customers)的主键id就自动增1,如下所示 create table customers -- 创建客户表 ( ...

  6. DBCP object created 日期 by the following code was never closed:

    1.分析 看到标题 DBCP 首先想到的肯定是 数据库连接池哪方面有问题,那么先别着急去解决,不要一股脑就钻进逻辑代码中,然后启用调试就开始一步一步 的分析.我们首先要做的就是想,想想数据库连接池,在 ...

  7. Android学习笔记(四) 定时器Timer

    Android考虑到线程安全问题,不允许在线程中执行UI线程. 所以在线程中不允许有UI操作 可以利用Handler机制来接收Timer每隔一秒发出的信息,也可以直接利用handler机制的 1.方法 ...

  8. 应用nslookup命令查看A记录、MX记录、CNAME记录和NS记录

    https://blog.csdn.net/qq_38058202/article/details/80468688

  9. 【PAT】1002. A+B for Polynomials (25)

    1002. A+B for Polynomials (25) This time, you are supposed to find A+B where A and B are two polynom ...

  10. python 函数操作

    四.函数 定义: #!/usr/local/env python3 ''' Author:@南非波波 Blog:http://www.cnblogs.com/songqingbo/ E-mail:qi ...