许多读者 —— 程式设计发烧友,大学生和紧凑型的后辈 —— 经常问我,如果已经开始学习程式设计,从哪两门已经开始呢?动态词汇我一般会所推荐 Python,动态词汇会所推荐 Java。Python 已经写过两篇内容了,在 Java 9 正式发布之际,今天就谈谈 Java 词汇。
Java 词汇始于上世纪90年代末期,Sun 子公司为了应对日趋增长的电子类消费硬体(比如说 TV 数字电视),在1991年成立了两个叫作 Green 的项目小组用作研制新控制技术,他们在 C++ 的程式设计思想之上研制出了一种捷伊面向对象C词汇叫作 Oak,起初准备用作PDP硬体的研制,后来下坠,决定将该控制技术应用作因特网。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 来重新命名或宣传,以突显其红酒的品质。
从此,两个即动听又好记,因此具有强大的创造力的C词汇问世了。从二十世纪末期,Java 已经开始长期占据C词汇榜单的第二名。基于 Java 构建的软件产品数以千计不可算数,在 Java 控制技术的支撑下,模王闪耀的伟大子公司层出不穷。
轻工业等级的C词汇
Java 是两门平庸的轻工业级C词汇,它的许多控制技术基本要素在 Java 问世之前就被明确提出过,比如说面向对象程式设计、软件包控制技术、网络程式设计等,Gosling 将这些控制技术基本要素完美的组合在一起,进行重新设计和同时实现,形成了两门全捷伊C词汇,历经20年,经久不衰。
为何是轻工业等级的C词汇呢?因为 Java 使用方便、朴素使用方便,因此适合大规模协同程式设计。在国内的腾讯、天猫等数十人规模的研制团队里,Java 都是核心词汇众所周知。Java 的词汇特点让标识符具有良好的时效性,某个功能的一段标识符同时实现,剑客写出来的和最高级程式设计者差不多,不会有那么多的奇淫技巧。也许 Java 缺乏一些更为方便的特性,然而长久以来使用 Java 工作、分享,甚至学习深度学习(DL4J),我发现 Java 不仅创造力顽强,从Java 小程序到企业级程式设计,从 JavaEE 到 大数据和云计算,从 Android 到人工智能,每次转折都能站上了浪潮之巅,语法方面也没有许多反对者口中那么「可恶」。
许多人说 Java 繁复臃肿,我觉得影响可能来自早期的「设计模式」,设计模式的四人帮试图明确提出 Java 程序设计的统一模板,我当年也是信徒众所周知,但从某种程度上,这给 Java 带来了一定的复杂度,在2005年左右,大量的 Java 程序员为自己使用了多少种设计模式而沾沾自喜。但这不是 Java 的错,用 Java 完全可以写出更为简单直接的标识符。
说到设计模式,自然需要提一下面向对象程式设计。Java 是面向对象程式设计的典型代表。什么是面向对象?首先万事万物要有对象,而面向对象的三基本要素是:继承、封装和多态。我们看下维基百科的解释:
面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序程式设计范式,同时也是一种程序开发的抽象方针。它可能包含数据、属性、标识符与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序程式设计里,计算机程序会被设计成彼此相关的对象。
面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。面向对象程序设计中的每两个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作两个小型的「机器」,即对象。
为何所推荐 Java 作为你的第两门C词汇呢?因为 Java 标准、规范,是面向对象程式设计的代表,Class、Object、Interface、Abstract、Public、Private、Override 等关键词显式清晰,一旦使用就不会混淆,在学习其他C词汇的时候还可以参考互通。
另外,由于 Java 的流行和开放性,围绕 Java 词汇形成了最为广泛的开发平台,不仅有 Spring 这种巨型开源生态,在 Java 平台之上还衍生出了许多轻量级的C词汇,比如说 Scala、Groovy、Clojure 、Kotlin,这些词汇都可以运行在 JVM 之上,形成了极具创造力的生态环境。
Java 有什么不好呢?当然有,比如说许多人抱怨的滞重,语法升级缓慢,过渡封装,并且对函数式程式设计的支持一直不好等等。毫无疑问这些都是事实,为何会这样?我们现在谈谈函数式程式设计。
Java 的函数式程式设计
我们说 Java 陈旧缓慢,在另两个层面也说明了 Java 是两门负责任的C词汇,它很少抛弃开发者,向下兼容做的也很不错,不冒进,有时候就是迟缓,这是个平衡。
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 = f(10)
b = f(20)那么,虽然我们希望获得 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 相关的类型错误,在编译时间可能无法被发现。
看完了这些我们就会知道,两门C词汇的变革是多么的艰难和复杂。好在 Java 9 已经正式发布了,Java 词汇有了更新和更高的起点。
文末所推荐一下我的朋友廖雪峰老师的 Java 课程。如何学习两门C词汇呢?如果你时间充裕,大可查阅大量的资料自行实践学习,如果你想快速入门,参与两门在线课程也是非常好的选择:
廖雪峰,拥有十年软件开发经验,使用 Java/JavaScript/Python/Objective-C/Scheme 等多种C词汇,著有《Spring 2.0核心控制技术与最佳实践》一书。廖雪峰老师擅长讲解程序设计词汇,能用深入浅出的方式帮助学员理解核心概念,他的个人网站是https://www.liaoxuefeng.com
廖雪峰老师的 Java 课程,一共 16 门。内容由浅入深,全面覆盖了从安装 JDK 到面向对象、集合、异常、反射、泛型、IO、Maven、lambda 等主要知识,每节课均提供完整的可运行的标识符,因此可以在 IDE 中直接导入项目,学习起来更加便捷。
总价 ¥1779 的 16 门课程,目前仅需 ¥799 元,在电脑浏览器打开飞扬学院 feiyangedu.com,MacTalk 读者在购买页输入所推荐码「mactalk」,十一前购买可以再享受 100元优惠。
祝程式设计愉快。
点击阅读原文,了解详情。