首页 » Java程序员修炼之道 » Java程序员修炼之道全文在线阅读

《Java程序员修炼之道》7.2 语言生态学

关灯直达底部

编程语言有多种不同的流派和类型。也就是说,不同的语言有不同的编码风格和编码方式。如果想掌握这些风格并让它们为你所用,你得弄明白这些差异以及如何给语言分类。

注意 这些分类可以帮助思考语言的多样性。尽管某些分法可能更清晰,但没有哪种是完美的。

最近几年,语言在添加新特性时有跨越各种分类的趋势。这就是说,你最好认为某种语言比其他语言“函数化程度更低”,或者“虽然是动态类型,但必要时也有可选的静态类型”。

我们将要讨论的分类是“解释型与编译型”、“动态与静态”、“命令式与函数式”,还有在JVM上重新实现的语言与原生语言。通常这些分类用来明确语言的边界,不要用学院化方式把它们当做完整精确的分类。

Java是运行时编译、静态类型的命令式语言。它强调安全性、代码清晰、性能,并乐于表现出一定程度的繁琐和死板(比如在部署中)。不同的语言可能侧重不同,比如动态类型的语言可能更看重部署速度。

我们先从解释型与编译型分类开始介绍。

7.2.1 解释型与编译型语言

解释型语言是那种源码是什么就执行什么的语言,不会在执行开始之前把整个程序转成机器码。编译型语言则不同,一开始就要用编译器把人类可读的源码变成二进制形式。

这种分别最近变模糊了。80年代和90年代早期这两类语言的边界还相当清晰:C/C++及类似的语言是编译型,Perl和Python是解释型。但Java同时兼具编译型和解释型两种特性,这一点我们已经在第1章讲过了。字节码的出现使这个界限更模糊了。人类肯定读不了字节码,但它也不是真正的机器码。

对于本书中要研究的JVM语言,我们划分的边界是该语言是否会将源码编译为类文件并且执行。不产生类文件的语言会由解释器(可能是用Java写的)逐行执行源码。有些语言既有编译器也有解释器,还有些既有解释器又有产生JVM字节码的即时编译器(JIT)。

7.2.2 动态与静态类型

在动态类型语言中,变量在不同时间可能会有不同的类型。我们以一小段简单的JavaScript代码为例,JavaScript是著名的动态语言。即便你不了解这种语言,也应该很容易理解下面的代码:

var answer = 40;answer = answer + 2;answer = /"What is the answer? /" + answer;  

在这段代码中,变量answer一开始被赋值为40,当然,是个数值。然后给它加上2,变成了42。之后我们给answer赋了个字符串值。这在动态语言中是非常普遍的技术,不会引起语法错误。

JavaScript解释器也能分清两种+操作符的用法。第一个+是数字相加——把2加到40上,而在下一行中,解释器能从上下文中推导出开发人员要做字符串合并。

注意 这里的关键是动态类型语言跟踪变量值的类型(比如数字或字符串)信息,而静态类型语言跟踪变量的类型信息。

静态类型非常适合编译型语言,因为所有类型信息都在变量上,跟变量的值没有关系。这样很容易在编译时推导潜在的类型系统违规行为。

动态类型语言把类型信息放在变量所持有的值上。也就是说很难提前发现类型违规行为,因为推导所需的信息直到运行时才能得到。

7.2.3 命令式与函数式语言

Java 7是典型的命令式语言。命令式语言把程序的运行状态建模为可修改的数据,用一系列指令来改变运行状态。因此,在命令式语言中,程序状态才是核心概念。

命令式语言主要分为两类。一种是过程语言,比如BASIC和FORTRAN。这种语言把代码和数据完全分开,有简单的代码操作数据范式。另外一种是面向对象(OO)语言,数据和代码(以方法的形式)共同封装在对象中。面向对象语言中或多或少地存在元数据(比如类信息)引入的额外结构。

函数式语言不同,它把计算本身当做最重要的概念。函数式语言跟过程语言一样对值进行操作,但它不会修改输入,而是像数学函数一样返回新值。

如图7-2所示,函数被看做“小处理机”,输入值并输出新值。它们没有任何自己的状态,并且把它们和任何外部状态绑在一起也没有任何意义。这就是说一切皆对象的世界观跟函数式语言的自然观点有些分歧。

图7-2 命令式和函数式语言

在接下来的三章里,每章重点介绍一种语言,并且都会以前面对函数式编程的讨论为基础。我们会从Groovy开始,它带“一点儿函数式风格”,用我们在7.1节讨论过的方式处理集合;然后是Scala,对FP的利用更加充分;最后是Clojure(纯粹的函数式语言,完全没有面向对象特性)。

7.2.4 重新实现的语言与原生语言

JVM语言之间的另一个重要区别是重新实现已有语言与专门以JVM为目标的划分。通常来说,那些专门以JVM为目标写的语言能把自己的类型系统跟JVM的原生类型结合得更紧密。

下面是三种重新实现已有语言的JVM语言。

  • JRuby在JVM上重新实现了Ruby语言。Ruby是一个动态类型的面向对象语言,有些函数式特性。它在JVM上基本算解释型的,但最近发布的版本中有一个运行时JIT编译器,在适当条件下可以生成JVM字节码。

  • Jython由Jim Hugunin在1997年发起,当时是为了在Python中使用高性能的Java类库。它在JVM上重新实现了Python,因此是动态的,总体还算面向对象语言。它的运作方式是先在内部生成Python字节码,然后再转换成JVM字节码。这使它能在Python典型的解释模式(看起来像)下工作。通过生成JVM字节码,并把结果类文件保存到硬盘上,它也能在预先(AOT)编译模式下工作。

  • Rhino最初是由Netscape开发的,后来转给Mozilla项目。它在JVM上提供了一个JavaScript实现。JavaScript是动态类型的面向对象语言(但和Java实现面向对象的方式截然不同)。Rhino既支持编译模式也支持解释模式,随Java 7一起发布(具体细节请参见com.sun.script.javascript包)。

最早的JVM语言

很难确定最早的JVM语言(除Java之外)是什么。可以肯定的是在1997年前后就出现了Kawa语言,它是一种Lisp语言。在那之后这些语言几乎呈现了爆炸式增长,因此追踪它们的历史太难了。

在编写本书时,猜测至少有200种JVM语言应该是合理的。不能说所有语言都很活跃或得到了广泛应用,但这个数字起码能表明JVM是一个非常活跃的语言开发和实现平台。

注意 在随Java 7推出的语言和VM规范里,所有对Java语言的直接引用都从VM规范中去掉了。Java现在只是运行在JVM上的众多语言中的普通一员,它不再享有特权了。

就像我们在第5章中讨论的,能让这么多不同的语言运行在JVM上的关键技术是类文件格式。任何能产生类文件的语言都可以认为是JVM上的编译型语言。

我们接下来会讨论多语言编程怎么变成了让Java程序员感兴趣的领域。我们会解释基本概念,为什么要给我们的项目选择一种备选的JVM语言以及如何操作。