最近在学习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. 消息队列ActiveMQ的使用详解

    通过上一篇文章 <消息队列深入解析>,我们已经消息队列是什么.使用消息队列的好处以及常见消息队列的简单介绍. 这一篇文章,主要带大家详细了解一下消息队列ActiveMQ的使用. 学习消息队 ...

  2. 全面了解 Nginx 主要应用场景

    前言 本文只针对Nginx在不加载第三方模块的情况能处理哪些事情,由于第三方模块太多所以也介绍不完,当然本文本身也可能介绍的不完整,毕竟只是我个人使用过和了解到过得.所以还请见谅,同时欢迎留言交流 N ...

  3. 《深入理解Java虚拟机》笔记--第十三章、线程安全与锁优化

    先保证并发的正确性,然后在此基础上来实现高效. 线程安全:     当多个线程访问一个对象时,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操 ...

  4. 转:google测试分享-SET和TE

    原文:  http://blog.sina.com.cn/s/blog_6cf812be0102vbnb.html 前端时间看了google测试之道,收获了一些,在此总结下并打算写一个系列blog,顺 ...

  5. java基础12 抽象类(及关键字:abstract)

    抽象类:abstract 1.应用的场景 我们描述一类事物时,存在着某种行为,但这种行为目前不具体,那么我们就可以抽取这种行为的声明,但是不去实现这种行为,我们就需要使用抽象类. 2.抽象的好处 强制 ...

  6. NTP路由器配置

    14.1. 路由器日志显示时间戳 提问 在路由器 的日志和排错信息里面显示时间 回答 Router#configure terminal Enter configuration commands, o ...

  7. JSP、EL表达式、JSTL

    JSP 1.什么是jsp? Java Server Pages: java服务器端页面.可以理解为一个特殊的页面,其中既可以指定定义html标签,又可以定义java代码.其本质就是一个Servlet. ...

  8. c#元组举例

    元组的概要: 数组合并了相同类型的对象,而元组合并了不同类型的对象.元组起源于函数编程语言(如F#) ,在 这些语言中频繁使用元组.在N盯4中,元组可通过.NET Fmmework用于所有的NET语言 ...

  9. Reflow(回流)和Repaint(重绘) (转)

    原文地址:http://blog.csdn.net/qq_18826911/article/details/68924255 首先我们要明白的是,页面的显示过程分为以下几个阶段: 1.生成DOM树(包 ...

  10. 洛谷 P1957 口算练习题 题解

    题目传送门 这道题是考字符串处理,另外输入要使用c++的cin的神奇功能. #include<bits/stdc++.h> using namespace std; int n;char ...