你的第一门编程语言

你的第一门编程语言

原文链接:你的第一门编程语言
作者:池建强 & 朱赟


很多读者 —— 编程爱好者,大学生和入门级的新人 —— 经常问我,如果开始学习编程,从哪一门开始呢?动态语言我一般会推荐 Python,静态语言会推荐 Java。Python 已经写过几篇内容了,在 Java 9 发布之际,今天就聊聊 Java 语言。

Java 语言始于上世纪90年代初期,Sun 公司为了应对日趋增长的电子类消费硬件(比如 TV 机顶盒),在1991年成立了一个叫做 Green 的项目小组用于研发新技术,他们在 C++ 的编程思想之上研发出了一种新的面向对象编程语言叫做 Oak,最初准备用于嵌入式硬件的研发,后来改变方向,决定将该技术应用于万维网。1995年,Oak 因为商标问题更名为 Java,1996年1月,JDK1.0 正式发布。


Java 的命名

这门语言为什么会叫 Java 呢?在硅谷有很多不同的版本,Java 之父 James Gosling 也提供了故事的版本之一。

最初这门语言的名称 OaK 的灵感来自 Gosling 办公室外的一棵茂密的橡树。但是申请商标的时候,发现 OaK 是另一个公司的名字,好了,现在这门语言需要一个新名字。Gosling 召开了一个命名征集会,大家提出了很多名字,最终按照评选次序排成列表,共大家选择。排在第一位的是 Silk(丝绸),尽管很多人都喜欢这个名字,但 Gosling 不喜欢。排在第二和第三的都没有通过律师这一关。Gosling 最喜欢的是排在第三位的 Lyric(抒情诗)。最终,排在第四位的名字得到了所有人的认可。这个名字就是Java。

Gosling 回忆,「我记得第一个提议名字 Java 的是 Mark Opperman」。Mark 是在一家咖啡店与同事喝咖啡时得到灵感的。Java 是印度尼西亚爪哇岛的英文名称,因盛产咖啡而闻名。国外的许多咖啡店用Java 来命名或宣传,以彰显其咖啡的品质。

从此,一个即好听又好记,并且具备强大的生命力的编程语言诞生了。从本世纪初期,Java 开始长期占据编程语言排行榜的第一名。基于 Java 构建的软件系统成千上万不可计数,在 Java 技术的支撑下,星光璀璨的伟大公司层出不穷。


工业级别的编程语言

Java 是一门中规中矩的工业级编程语言,它的很多技术要素在 Java 诞生之前就被提出过,比如面向对象编程、虚拟机技术、网络编程等,Gosling 将这些技术要素完美的组合在一起,进行重新设计和实现,形成了一门全新的编程语言,历经20年,长盛不衰。

为什么是工业级别的编程语言呢?因为 Java 功能强大、简朴易用,并且适合大规模协作编程。在国内的阿里巴巴、京东等数千人规模的研发团队里,Java 都是核心语言之一。Java 的语言特点让代码具备良好的可读性,某个功能的一段代码实现,高手写出来的和初级编程者差不多,不会有那么多的奇淫技巧。也许 Java 缺乏一些更为方便的特性,然而长久以来使用 Java 工作、分享,甚至学习深度学习(DL4J),我发现 Java 不仅生命力顽强,从Java 小程序到企业级编程,从 JavaEE 到 大数据和云计算,从 Android 到人工智能,每次转折都能站上了浪潮之巅,语法方面也没有很多反对者口中那么「可恶」。

很多人说 Java 繁复臃肿,我觉得影响可能来自早期的「设计模式」,设计模式的四人帮试图提出 Java 程序设计的统一模板,我当年也是信徒之一,但从某种程度上,这给 Java 带来了一定的复杂度,在2005年左右,大量的 Java 程序员为自己使用了多少种设计模式而沾沾自喜。但这不是 Java 的错,用 Java 完全可以写出更为简单直接的代码。

说到设计模式,自然需要提一下面向对象编程。Java 是面向对象编程的典型代表。什么是面向对象?首先万事万物要有对象,而面向对象的三要素是:继承、封装和多态。我们看下维基百科的解释:

面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程范式,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象。

面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个小型的「机器」,即对象。

为什么推荐 Java 作为你的第一门编程语言呢?因为 Java 标准、规范,是面向对象编程的代表,Class、Object、Interface、Abstract、Public、Private、Override 等关键词显式清晰,一旦使用就不会混淆,在学习其他编程语言的时候还可以参考互通。

另外,由于 Java 的流行和开放性,围绕 Java 语言形成了最为广泛的开发平台,不仅有 Spring 这种巨型开源生态,在 Java 平台之上还衍生出了很多轻量级的编程语言,比如 Scala、Groovy、Clojure 、Kotlin,这些语言都可以运行在 JVM 之上,形成了极具生命力的生态环境。

Java 有什么不好呢?当然有,比如很多人抱怨的滞重,语法升级缓慢,过渡封装,并且对函数式编程的支持一直不好等等。毫无疑问这些都是事实,为什么会这样?我们现在聊聊函数式编程。


Java 的函数式编程

我们说 Java 陈旧缓慢,在另一个层面也说明了 Java 是一门负责任的编程语言,它很少抛弃开发者,向下兼容做的也很不错,不冒进,有时候就是迟缓,这是个平衡。

2016年 Airbnb 的女博士安姐给我写了篇 Java 函数式编程的文章,雪藏了很久,今天终于舍得放出来了。她对 Java 函数式编程的看法如下:

关于 Java 的设计者,还有一些事,印象不是特别深了,但是记得当时颇受感触的两点:一是他们对于选择哪些函数进 core libarary 的谨慎程度,因为 Java 早期是很轻量级的,后来的版本,功能越来越强大,但是语言本身也越来越沉重,这也是为什么很多人喜欢新出的 Scala。二是实现函数库的语言开发者对每个函数的精度和运行时间的吹毛求疵到了令人发指的程度,听说他们有时候读无数的论文,看无数的实现,做大量的比较,就为了敲定到底应该在最终的函数中使用哪一种实现方式。比如浮点数是有 rounding error 的,那么一个数值计算中先算哪一步后算哪一步带来结果都可能是不同的。而实现中的考虑,往往为了小数点后面十几位以后的一个 1,组里也要反复斟酌很久。

经常偶尔看到有人聊到 lambda,只会说那是一种 anonymous function 的方式。为什么 lambda 的概念到 Java 8 才有了实现?之前的 Java 版本,包括很多其他语言都没有真正的 lambda 实现呢?这其实是程序设计语言里的一个很基本的概念。

假如我有一个 lambda 表达式,用伪代码来写,可以写成:

def f(x)
     def g()
         return x
     end
     return g
 end

这个 lambda 表达式可以看到 f(10) = 10, f(20) = 20.

在一个没有 lambda 支持,或者嵌套式函数定义支持的语言中 —— 比如 C 语言,这个可能会实现成:

typedef int (*fp_t)() ;

int g () {
  return x ;
}

fp_t f(int x) {
  return g ;
}

但是问题就在于,g 函数中的 x 是没有定义的,程序不可能编译运行。解决这个问题,我们可以引入一个全局的 x 变量,在对函数 f 进行定义的时候,给这个全局 x 赋值。但是由于 C语言不能每次运行时定义一个新的函数,因此,如果赋值


那么,虽然我们希望得到 a=10, b=20, 但是上面的实现只能给我们 a=20, b=20。

所以看得出,仅仅的一个 anonymous function, 或者函数指针,是不足以正确的实现 lambda 的。而正确实现 lambda,或者说允许把 lambda 表示的函数作为一个像其他类型的值一样作为参数来传递,语言必须要有对 lambda 的函数表达,以及一个用来在各层中传递参数值的的「参数定义环境」两者同时的实现。这也就是函数语言中的 closure 的概念。换句话说,实现 lambda 可以作为一个普通类型一样的值来存储和传递,我们需要一个 closure,而 closure 可以看成:

closure = lambda 表达式 +纪录所有函数局部变量值在每一层 lambda 中的赋值的一个环境。

实现 closure 大体有两种方式。一种叫做「自底向上」的 closure 转变,也称为 flat closure。它从函数定义的最里层,将每一层的局部函数变量值拷贝到次里层。每一层的变量可能重名,而这就需要变量名解析的技术,对变量按层重命名。这样逐层拷贝,最后形成一个 lambda 对应的单层的变量赋值环境。

另一种叫做「自顶向下」的closure 转变,也称为 shared closure。它从函数定义的最外层,将每一层的局部函数变量赋值用类似指针的方式传播共享到里层的 lambda。这种实现的好处是避免的重命名和拷贝,但是实现赋值环境的共享其实是很棘手的。

总而言之,lambda 在语言中的实现是复杂并且昂贵的。不仅容易出错,还给语言的垃圾收集 GC 带来新的挑战。它也让语言的 type system 的所有证明和推导变的复杂无比。虽然现在主流的语言都提供了 lambda 的实现。但用起来还是有一定限制也需要一些谨慎的。比如, C 语言仍然不支持嵌套式的函数定义。C++11 增加了对 closure 的支持,但是因为语言本身没有 GC 的原因,使用起来需要异常谨慎,很容易引起 dangling references。比如,Ruby,函数不能直接作为参数传递,而是通过 Method 或者 Proc 来使用。且函数的嵌套定义并没有很好的对 scope 进行嵌套。而 Java 8,虽然有了对 lambda 的支持,但是 Java 的 type system,并没有对函数 type 有任何的支持。换句话说,Java 8 中其实并没有对 function types 的type system 的实现,这就意味着一些 lambda 相关的类型错误,在编译时间可能无法被发现。

看完了这些大家就会知道,一门编程语言的变革是多么的艰难和复杂。好在 Java 9 已经发布了,Java 语言有了更新和更高的起点。

阅读原文

编辑于 2017-09-25

文章被以下专栏收录

    不定期更新互联网技术知识点、总结归纳、整理开发技巧,相互学习,共同进步! 不定期更新移动端的产品研究、见解、归纳。 分享当前主流的界面设计尺寸、主流平台的设计规范、主流APP的设计规范。 设计类干货资源整合,设计类技能教程等。