本文共 20970 字,大约阅读时间需要 69 分钟。
天地和而万物生,阴阳接而变化起。《荀子·礼记》
所谓编程语言只是一个抽象的规范,而编译器是这个规范的实现,它是在这个规范的严格定义下被实现的.
打个比方,这个语言规范,就像是一份制造汽车的图纸,他规定了汽车应该有什么,拥有什么功能,等等各个方面,而编译器就是一辆根据这张图纸制造出来的汽车,它实现了图纸的定义,这样你才能真正的使用这辆汽车。而我们用这门语言写源代码去解决一个问题的过程,就好比是你开着这辆车,去你想去的各个地方,享受着这辆车的驾驶的乐趣的过程。
这辆车本身设计的好坏,以及制造出来的质量如何,很大程度上影响了我们使用车的过程。类似的,这门语言本身设计的理念思想哲学,工程架构合理性,易用性等等,以及这个编译器实现的质量,很大程度上影响了我们使用这门语言的过程。
编程语言和编译器的关系,可以用面向对象编程思想里面的类和对象的关系来类比。他们谁也离不开谁,只有两部分共同合作,你才能使用这个语言.
学习一门语言大概会经历如下几步。
1.基本语法
学习任何东西,都是一个由表及里的过程。学习一门编程语言也一样。对于一门编程语言来说,“表” 就是基本词汇和语法。
对于基础语法的学习,我们可以看一些简短而又系统的教程。
2.编码实践
所谓“纸上得来终觉浅,绝知此事要躬行”是也。此处就不多说。
掌握基础,持续练习
每一门编程语言的学习内容都会涉及:
运行环境
数据类型(数字、字符串、数组、集合、映射字典等) 表达式 函数 流程控制 类、方法
等等,不同的语言还有一些不同的特性,这些内容并不复杂,尽快通过大量的练习击倒它们,然后再去深入了解面向对象编程OOP、函数式编程FP、并发、异常、文件IO、网络、标准库等内容,并辅以持续的练习,这些内容才能够让你真正进入编程领域并做出实际的软件。
学习一门新的语言的时候,要利用以前所学的语言的功底,但是也要保持开放的心态。
3.技近乎道
基础语法学习,能让你快速上手,应用实践。对技巧和坑的关注,一定程度上拓展了你的知识面。系统学习,一方面会进一步拓展你的知识面。另一方面,也有利于你语言知识结构的形成。
任何一门成熟语言,都有其特有的生态。这个生态包括: 框架,扩展包,解决方案,模式, 规范等。
在不断编码实践过程中,我们逐步熟练使用很多API库、框架,也不断踩坑填坑、看源代码、不断解决问题,不断加深对语言的理解,同时会看一些优秀的框架源代码。
如果还有精力,我还会去学习下语言更底层的东西。而不仅仅停留在应用层面。如Java中的集合类实现的算法与数据结构,如JVM是如何执行Java代码的。如Java的线程和操作系统线程的关系。以及一些操作系统方面的知识。
最后,达到游刃有余的境界。这一层级,基本可入武林高手之列了。
庖丁释刀对曰:“臣之所好者,道也,进乎技矣。始臣之解牛之时,所见无非牛者。三年之后,未尝见全牛也。方今之时,臣以神遇而不以目视,官知止而神欲行。依乎天理,批大郤,导大窾,因其固然,技经肯綮之未尝,而况大軱乎!
这里的“牛”,可以理解为我们所说的各种编程思想,编程范式,编程方法,编程技巧等等。最后,达到“运用之妙,存乎一心”之境也。
4.创造新世界
编程的本质就是创造世界。
达到这个境界的,基本都是世界顶尖大牛了。
编程语言发展史上的杰出人物:
约翰·冯·诺伊曼,操作系统概念的发起者。
肯·汤普逊,发明了Unix。 丹尼斯·里奇,发明了C。 约翰·巴科斯,发明了Fortran。 阿兰·库珀,开发了Visual Basic。 詹姆斯·高斯林,开发了Oak,该语言为Java的先驱。 安德斯·海尔斯伯格,开发了Turbo Pascal、Delphi,以及C#。 葛丽丝·霍普,开发了Flow-Matic,该语言对COBOL造成了影响。 肯尼斯·艾佛森,开发了APL,并与Roger Hui合作开发了J。 比尔·乔伊,发明了vi,BSD Unix的前期作者,以及SunOS的发起人,该操作系统后来改名为Solaris。 艾伦·凯,开创了面向对象编程语言,以及Smalltalk的发起人。 Brian Kernighan,与丹尼斯·里奇合著第一本C程序设计语言的书籍,同时也是AWK与AMPL程序设计语言的共同作者。 约翰·麦卡锡,发明了LISP。 比雅尼·斯特劳斯特鲁普,开发了C++。 尼克劳斯·维尔特,发明了Pascal与Modula。 拉里·沃尔,创造了Perl与Perl 6。 吉多·范罗苏姆,创造了Python。 ......正是这些各个编程领域的引领者们,才使得我们这个世界更加美好。
1801,Joseph Marie Jacquard用打孔卡为一台织布机编写指令,在提花织布机(或称甲卡提花织布机,Jacquard loom)上,运用打孔卡上的坑洞来代表缝纫织布机的手臂动作,以便自动化产生装饰的图案。Jacquard织布机是第一台可进行程序控制的织布机。用打孔卡进行编程的概念,直到电子计算机被发明出来之后仍然被广泛运用。
1842,爱达·勒芙蕾丝(Ada lovelace)在1842年至1843年间花费了九个月,将意大利数学家Luigi Menabrea关于查尔斯·巴贝奇新发表机器分析机的回忆录翻译完成。分析机由于其设计思想过于先进,在当时根本没有 被制造出来。(Babbage的分析机一般被认为是现代电子通用计算机的先驱)。她于那篇文章后面附加了一个用分析机计算伯努利数方法的细节,被认为是世界上第一个电脑程序。 “她的努力只遇到了一点点小小的麻烦,那就是:实际上并没有任何计算机能够用来运行她的程序。后来的企业架构师们重新吸收了她的这个技能,用来学习如何更好地使用UML进行编程。”[8]
1890,霍列瑞斯(Herman Hollerith)在观察列车长对乘客票根在特定位置打洞的方式后,意识到他可以把信息编码记载到打孔卡上,随后根据这项发现使用打孔卡来编码并纪录1890年的人口统计数据(这种语言是种编码)。
1936, Alan Turing发明了世间一切程序语言的最终形态,但很快他就被英国军情六处“请”去当007了。与通用图灵机(Universal Turing machine)等价的语言被称为图灵完备的(Turing completeness),它定义了“什么样的语言可以被称作是程序语言”。
1936 , Alonzo Church同时也发明了世间一切程序语言的最终形态,甚至做得更好。他发明的λ演算是当今函数式编程(FP)的鼻祖,对函数式编程有巨大的影响,特别是Lisp 语言。Church是Turing在Princeton的博士生导师,他在λ演算方面的工作先于Turing指出了不存在一个对可判定性问题的通用解法,这后来证明和Turing针对停机问题提出的图灵机模型是等价的。即著名的“Church-Turing”论题。
最早被确认的使用电的计算机诞生在1940年代。
程序员在有限的速度及存储器容量限制之下,撰写汇编程序。用汇编语言的这种撰写方式需要花费大量的脑力而且很容易出错。
康拉德·楚泽于1948年发表了他所设计的Plankalkül编程语言的论文。但是在他有生之年却未能将该语言实现。(关于康拉德·楚泽的故事,可参考本书:附录1)
在这段期间被开发出来的重要语言包括有:
1943 - Plankalkül (Konrad Zuse)1943 - ENIAC coding system1949 - C-10
在这段期间被开发出来的重要语言有:
1951 - Regional Assembly Language1952 - Autocode1954 - FORTRAN1954 - IPL (LISP的先驱)1955 - FLOW-MATIC (COBOL的先驱)1957 - COMTRAN (COBOL的先驱)1958 - LISP1958 - ALGOL 581959 - FACT (COBOL的先驱)1959 - COBOL1962 - APL1962 - Simula1962 - SNOBOL1963 - CPL (C的先驱)1964 - BASIC1964 - PL/I1967 - BCPL (C的先驱)
其中有三个现代编程语言于1950年代被设计出来,这三者所派生的语言直到今日仍旧广泛地被采用:
名称取自"FORmula TRANslator"(公式翻译器),约翰·贝克斯(John Backus)针对汇编语言的缺点而研究开发的语言。
名称取自"LISt Processor"(枚举处理器),约翰·麦卡锡(John McCarthy)在1958年基于λ演算所创造,采用抽象数据列表与递归作符号演算来衍生人工智能。LISP为函数式程序设计语言,所有运算都能以函数作用于参数的方式来实现。LISP核心的操作符只有7个:quote、atom、eq、car、cdr、cons、cond。前三者quote、atom、eq用于符号的推断;car、cdr、cons操纵表格;cond负责分支判断。这种简洁定义,非常接近图灵机原型的纯函数式语言,是现代语言完全无法比拟的。
名称取自"ALGOrithmic Language"(算法语言)。ALGOL 60是程序设计语言由技艺转向科学的重要标志,其特点是局部性、动态性、递归性和严谨性,发明于1960年。ALGOL 60强化了当时许多关于计算的想法,并提出了两个语言上的创新功能:
(1)嵌套区块结构(Nested block structure):可以将有意义的代码片段组群成一个区块(block),而非转成分散且特定命名的程序。
(2)词汇范围(lexical scoping):区块可以有区块外部无法通过名称访问,属于区块本身的变量、程序以及函数。
另一个创新则是关于语言的描述方式:
一种名为巴科斯-诺尔范式 (BNF)的数学化精确符号被用于描述语言的语法。之后的编程语言几乎全部都采用类似BNF的方式来描述程序语法中上下文无关的部分。
Pascal、Ada、Simula、C等都借鉴了ALGOL。
名称取自"COmmon Business Oriented Language"(通用商业导向语言),由格雷斯·霍波(G.Hopper)所开发。COBOL语言以代码极其冗长和通篇大写字母的书写风格而闻名。据称用COBOL书写的程序超过了2000亿行。另有调查发现世界上目前使用的商业应用软件之中的百分之七十是用COBOL代码编写的。
BASIC (初学者通用符号指令代码,Beginners' All-purpose Symbolic Instruction Code),匈牙利人约翰·凯梅尼(John G. Kemeny)与数学教师托马斯·卡茨(Thomas E. Kurtz)认为像FORTRAN那样的语言都是为专业人员设计,没有办法普及。于是,他们在简化FORTRAN的基础上由共同研制出来的。1964年BASIC语言正式发布。第一个BASIC程序在1964年5月1日早上4时,由BASIC编译程序进行编译后成功运行 。1975年,比尔·盖茨把它移植到PC上。
在1960年代以及1970年代中,结构化程序设计的优点也带来许多的争议,特别是在程序开发的过程中完全不使用GOTO。
在这段期间被开发出来的重要语言有:
1968 - Logo1970 - Pascal1970 - Forth1972 - C语言1972 - Smalltalk1972 - Prolog1973 - ML1975 - Scheme1978 - SQL (起先只是一种查询语言,扩充之后也具备了程序结构)
1960年代晚期至1970年代晚期的期间中,编程语言的发展也有了重大的成果。大多数现在所使用的主要语言范式都是在这段期间中发明的。
第一个面向对象编程语言。由挪威科学家Ole-Johan Dahl和Kristen Nygaard,以Algol 60超集的方式设计开发。
Niklaus Wirth(就是那位说:“算法+数据结构=程序” 的人)创造了Pascal,一个过程式的语言。Pascal语言语法严谨,层次分明,程序易写,具有很强的可读性,是第一个结构化的编程语言。Pascal的名称是为了纪念十七世纪法国著名哲学家和数学家Blaise Pascal。
源自Ken Thompson发明的B语言,而 B语言则源自BCPL语言。1967年,剑桥大学的Martin Richards对CPL语言进行了简化,于是产生了BCPL(Basic Combined Programming Language)语言。1970年,美国贝尔实验室的 Ken Thompson,以BCPL语言为基础,设计出很简单且很接近硬件的B语言(取BCPL的首字母)。并且他用B语言写了第一个UNIX操作系统。
1971年,Dennis M.Ritchie伙同Thompson一起,合作开发UNIX。1972年, D.M.Ritchie 在B语言的基础上设计出C语言。Thompson和Ritchie就用C完全重写了UNIX。在开发中,他们还考虑把UNIX移植到其他类型的计算机上使用。C语言强大的移植性(Portability)在此显现。机器语言和汇编语言都不具有移植性,为x86开发的程序,不能在Alpha,SPARC和ARM等机器上运行。而C语言程序则可以使用在任意架构的处理器上,只要那种架构的处理器具有对应的C语言编译器和库,然后将C源代码编译、连接成目标二进制文件之后即可运行。未来的JVM的“虚拟机”的思想,在某种程度上正是源自这里,通过对不同平台上JVM的实现,向上封装一层,从而使得基于JVM的编程语言可以更大限度的实现了跨平台(当然,对应需要实现各个平台上(比如说Windows,Linux,Mac OS)的JDK)。
由Alan Kay,Dan Ingalls,Ted Kaehler,Adele Goldberg等于70年代初在Xerox PARC开发的面向对象编程语言。
Alan Kay 总结了 Smalltalk 的五大基本特征。这是第一种成功的面向对象程序设计语言,也是Java 的基础 语言。通过这些特征,我们可理解“纯粹”的面向对象程序设计方法是什么样的:
(1) 所有东西都是对象。可将对象想象成一种新型变量;它保存着数据,但可要求它对自身进行操作。理论 上讲,可从要解决的问题身上提出所有概念性的组件,然后在程序中将其表达为一个对象。 (2) 程序是一大堆对象的组合;通过消息传递,各对象知道自己该做些什么。为了向对象发出请求,需向那 27 个对象“发送一条消息”。更具体地讲,可将消息想象为一个调用请求,它调用的是从属于目标对象的一个 子例程或函数。 (3) 每个对象都有自己的存储空间,可容纳其他对象。或者说,通过封装现有对象,可制作出新型对象。所 以,尽管对象的概念非常简单,但在程序中却可达到任意高的复杂程度。 (4) 每个对象都有一种类型。根据语法,每个对象都是某个“类”的一个“实例”。其中,“类”(Class) 是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?”。 (5) 同一类所有对象都能接收相同的消息。[10]Smalltalk对其它众多的程序设计语言的产生起到了极大的推动作用,例如:Objective-C,Actor, Java 和Ruby等。90年代的许多软件开发思想得利于Smalltalk,例如Design Patterns, Extreme Programming(XP)和Refactoring等。
Prolog语言最早由Aix-Marseille大学的Alain Colmerauer与Phillipe Roussel、Kowalski等人于60年代末研究开发。它建立在逻辑学的理论基础之上, 最初被运用于自然语言等研究领域。现已广泛的应用在人工智能的研究中,可以用来建造专家系统、自然语言理解、智能知识库等。
ML(Meta Language)是Robin Milner主管LCF项目时(1970),作为LCF项目的元语言(Meta Language)而设计的,这也是其名字的来历。LCF项目是受Dana Scott给出的一组逻辑原则启发而设立的,致力于开发一种“可计算函数逻辑”(Logic of Computable Functions)。目标是构造一个方便实用的系统,来自动的或者半自动的证明函数程序中一些有趣的性质。今天,大多数著名的推理系统都是用ML写的。目前ML有两个发展分支:Standard ML和Caml。
ML使用了Hindley-Milner类型推论算法来推测大多数值的类型,而不需要四处使用注解。ML一般被归为非纯函数式编程语言,因为它允许副作用和指令式编程。这一点和纯函数式编程语言例如Haskell很不一样。ML特性有惰性求值的求值策略,一阶类型函数, 带有垃圾收集的自动内存管理, 参数多态,静态数据类型,类型推断,代数数据类型,模式匹配和异常处理等。ML中的思想影响了众多的语言,例如Haskell,Cyclone和Nemerle。
这些语言都各自演展出自己的家族分支,现今多数现代编程语言的祖先都可以追溯他们其中至少一个以上。
1980年代的编程语言与之前相较显得更为强大。C++合并了面向对象以及系统程序设计。美国政府标准化一种名为Ada的系统编程语言并提供给国防承包商使用。日本以及其他地方运用了大量的资金对采用逻辑编程语言结构的第五代语言进行研究。函数编程语言社区则把焦点转移到标准化ML及Lisp身上。这些活动都不是在开发新的范式,而是在将上个世代发明的构想进一步发扬光大。
然而,在语言设计上有个重大的新趋势,就是研究运用模块或大型组织化的程序单元来进行大型系统的开发。Modula、Ada,以及ML都在1980年代发展出值得注意的模块化系统。模块化系统常拘泥于采用泛型程序设计结构:泛型存在(generics being)、本质(essence),参数化模块(parameterized modules)。(参阅多态)
尽管没有出现新的主要编程语言范式,许多研究人员仍就扩充之前语言的构想并将它们运用到新的内容上。举例来说,Argus以及Emerald系统的语言配合面向对象语言运用到分布式系统上。
1980年代的编程语言实现情况也有所进展。计算机系统结构中RISC的进展假定硬件应当为编译器设计,而非身为人类的汇编语言程序员。借由中央处理器速度增快的帮助,编译技术也越来越积极,RISC的进展对高级语言编译技术带来不小的关注。
语言技术持续这些发展并迈入了1990年代。
在这段期间被开发出来的重要语言包括有:
1980 - Ada1983 - C++ (加上类的C)1984 - Common Lisp1985 - Eiffel1986 - Erlang1987 - Perl1988 - Tcl1989 - FL (Backus)
Bjarne Stroustrup,他使用过Simula和ALGOL,接触过C。他对Simula的类体系感受颇深,对ALGOL的结构也很有研究,深知运行效率的意义。既要编程简单、正确可靠,又要运行高效、可移植。于是Bjarne Stroustrup以C为背景,以Simula思想为基础,把他所听说过的一切都试图嫁接到C上,创造出了C++。它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计,因而C++就适应的问题规模而论,大小由之。
1990年代未见到有什么重大的创新,大多都是以前构想的重组或变化。这段期间主要在推动的哲学是提升程序员的生产力。
许多"快速应用程序开发" (RAD) 语言也应运而生,这些语言大多都有相应的集成开发环境、垃圾回收等机制,且大多是先前语言的派生语言。这类型的语言也大多是面向对象的编程语言,包含有Object Pascal、Visual Basic,Java以及C#。
1995年,互联网的蓬勃发展给了Oak(Java之前的名字)机会。Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
我们将在下一章中介绍Java编程简史。另外,关于上面提到的“提升程序员的生产力”的哲学理念,本书介绍的SpringBoot框架,就是为了推动程序员的生产力而设计开发的。不管在编程语言设计领域,还是在编程框架的开发领域,很大一部分的目的,就是为了这一点。
在这段期间被开发出来的重要语言包括有:
1990 - Haskell1991 - Python1991 - Visual Basic1993 - Ruby1993 - Lua1994 - CLOS (part of ANSI Common Lisp)1995 - Java1995 - Delphi (Object Pascal)1995 - JavaScript1995 - PHP1997 - REBOL1999 - D
编程语言持续在学术及企业两个层面中发展进化,目前的一些趋势包含有:
在语言中增加安全性与可靠性验证机制:额外的堆栈检查、信息流(information flow)控制,以及静态线程安全。
提供模块化的替代机制:混入(mixin)、委派(delegates),以及观点导向。
组件导向(component-oriented)软件开发。
元编程、反射或是访问抽象语法树(Abstract syntax tree)
更重视分布式及移动式的应用。
与数据库的集成,包含XML及关系数据库。
支持使用Unicode编写程序,所以源代码不会受到ASCII字符集的限制,而可以使用像是非拉丁语系的脚本或延伸标点符号。
图形用户界面所使用的XML(XUL、XAML)。
等等。
在这段期间被开发出来的重要语言包括有:
2001 - C#2001 - Visual Basic .NET2002 - F#2003 - Scala2003 - Factor2006 - Windows PowerShell2007 - Clojure2009 - Go2014 - Swift (编程语言)
编程语言设计我们需要一个抽象的逻辑规范X,这个规范规定了这门语言的基本词汇,语法,运算符,表达式,数据类型,等等。设计的语言必须满足图灵完备性,才能够表达任何一个可计算的问题。
然后,我们在这个规范下实现一个编译器Compiler,可以翻译在这个规范下写出来的语言文本(即源代码)。翻译成的目标语言,就是操作系统可执行的机器码(遵循其CPU指令集规范)或者是虚拟机VM可执行的字节码(遵循VM规范的指令集)。
现代很多语言的编译器是用自己写成的——只要你有一个其他语言写的编译器来让这个自解释循环启动起来。
不管是exe可执行文件还是Linux,Mac OS下的程序,都是一些二进制代码,我们称之为机器语言。这些代码的执行和操作系统以及CPU硬件都有关。
大部分情况下,编译器是一种将高级语言翻译成机器语言的程序。这个程序本身也是一些机器语言的代码。机器代码是可以直接运行的。
无论是高级语言、汇编语言还是机器语言,实际上都是等价的,唯一有区别的是,越高级的语言,我们人类写起来越容易,所以有那么多的高级语言代码不断被设计发明出来。说不定哪一天,人类可以发明出可以直接写程序的“程序”——机器程序员。
计算机领域中的所有问题,都可以通过向上一层进行抽象封装来解决.这里的封装的本质概念,其实就是”映射“。
就好比通过的电子电路中的电平进行01逻辑映射,于是有了布尔代数,数字逻辑电路系统;
对01逻辑的进一步封装抽象成CPU指令集映射,诞生了汇编语言;
通过汇编语言的向上抽象一层编译解释器,于是有了pascal,fortran,C语言;
再对核心函数api进行封装形成开发包(Development Kit), 于是有了Java,C++ 。这一切的过程都是向上层抽象映射的过程。从面向过程到面向对象,再到设计模式,架构设计,面向服务,各种软件理论五花八门,但万变不离其宗——
你要解决一个怎样的问题?
你对这个世界的本质认知是怎样的? 你要解决领域的问题是什么? 等等。编程的本质跟大自然创造万物的本质是一样的。
二进制的“0”和“1”通过计算机里能够创造出一个虚拟的、纷繁的世界。自然界中的阴阳形成了现实世界的万事万物。
所以自然世界的“阴”、“阳”作为基础,切实地造就了复杂的现实世界,计算机的“0”和“1”形象地模拟现实世界的一切现象,易学中的“卦”和“阴阳爻”抽象地揭示了自然界存在的事件和其变化规律。
另外,阴阳就是在同一个属概念“对立统一(全集U)”下的两个种概念“阴(集X)和阳(集Y)”之间的一种不相容关系。阴阳的内涵互相否定,一个概念“阴集”肯定对象阴的属性,而否定“阳”的属性;另一个概念“阳集”则以否定阴概念所肯定的属性而肯定“阳”的属性;
阴阳的外延互相排斥
X∩Y=0
而又互补
X∪Y=U=1
1-Y=X其总和等于它们最邻近的属概念(对立统一整体)的外延,即两个种概念阴阳外延的和或并
X+Y=X∪Y=U
OK,这就是我们熟知的,数理逻辑里面的所谓的bool代数了。
布尔代数起源于数学领域,是一个用于集合运算和逻辑运算的公式:〈B,∨,∧,¬ 〉。其中B为一个非空集合,∨,∧为定义在B上的两个二元运算,¬为定义在B上的一个一元运算。
通过布尔代数进行集合运算可以获取到不同集合之间的交集、并集或补集,进行逻辑运算可以对不同集合进行与、或、非。
一切皆是映射。
人的生命只有一次。生命太短暂,所以不要去做一些重复无聊的事情。能交给计算机做的,就尽量交给计算机去做。此乃编程之滥觞之地。
未来人工智能将取代大部分的重复手工劳动。将大大解放人类的劳动力,从而使得人类能够花更多的时间和精力,去创造去创新。而人工智能的本质,就是对人类智能的抽象建模。我们人类写的操作系统、浏览器、办公软件、画图设计工具、3D建模软件、电商系统、金融平台、社交APP,不就是另一种层次上的人工智能吗?这些东西,背后都是01的映射。当然,01背后是物理层次的,量子微观的世界了。
纵览整个计算机的发展史,最重要的思想非“抽象”莫属。
一层层的抽象封装了实现的细节,计算机开疆扩土,南征北战,发展到了今天蔚为壮观的互联网,云计算,大数据,机器智能的时代。
同时,也使得程序员写代码,从最初的拿着符号表在纸袋上打孔,到使用近似自然语言的高级编程语言来编程(当然背后少不了编译器、解释器,还有的是先通过虚拟机中间字节码这一层,再通过解释器映射到机器码,最后在硬件上作高低电平的超高频率的舞蹈),以及当今各种库API、框架、集成开发工具集,智能化的编码提示,代码生成等等技术,使得我们现在程序员,能更多的去关注问题本身以及逻辑的实现。
从只有少数技术人会用的命令行的操作系统unix、dos,到人性化的GUI图形界面操作系统,以及移动互联网时代的智能设备,计算机越来越融入到人类生活的方方面面。
正如解决数学问题通常我们会谈“思想”,诸如反证法、化繁为简等,解决计算机问题也有很多非常出色的思想。思想之所以称为思想,是因为“思想”有拓展性与引导性,可以解决一系列问题。
解决问题的复杂程度直接取决于抽象的种类及质量。过将结构、性质不同的底层实现进行封装,向上提供统一的API接口,让使用者觉得就是在使用一个统一的资源,或者让使用者觉得自己在使用一个本来底层不直接提供、“虚拟”出来的资源。
计算机中的所有问题 , 都可以通过向上抽象封装一层来解决。
Java虚拟机对各个平台而言,实质上是各个平台上的一个可执行程序。例如在windows平台下,java虚拟机对于windows而言,就是一个java.exe进程而已。
我们经常说的Java语言是平台无关的,跨平台的。其实这是针对从Java源代码到JVM字节码这一层是平台无关的(这一层的javac是Java代码写的,依然是跨平台)。
但是,真正到了把JVM字节码通过解释器映射到不同平台(操作系统,CPU硬件架构)上,JVM就必须针对各个平台实现一套解释器。只是这一层通过抽象封装,对我们程序员而言已经完全透明,无需做相关的工作而已。
在解释器层,作为JVM的设计开发者们(他们用的是C/C++),那就享受不到类似Java、Scala这样基于JVM跨平台的语言的特性了。
1.7.1 Java源代码编译执行过程
代码编译是由Java源码编译器来完成,流程图如下所示:
Java代码编译和执行的整个过程包含了以下三个重要的机制:
Java源码编译机制 类加载机制 类执行机制Java源码编译机制
Java 源码编译由以下三个过程组成: 分析和输入到符号表 注解处理 语义分析和生成class文件流程图如下所示:
最后生成的class文件由以下部分组成:
结构信息。包括class文件格式版本号及各部分的数量与大小的信息 元数据。对应于Java源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池 方法信息。对应Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息类加载机制
JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:1.7.2 Java Class文件简介
1.7.3 JVM字节码执行过程
JVM(Java虚拟机)是可运行Java代码的假想计算机。只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行。
编译成不依赖JVM的方式类似C/C++这种,需要为不同平台(操作系统/CPU)各自编译一份,也就是源代码跨平台。Java这种是二进制跨平台,因为平台的区别被JVM填了,或者说JVM本身就是平台。
字节码是不依赖操作系统平台的,字节码也不是专属于java语言的的,其他语言也可以编译成为字节码。只要符合字节码规范即可都可以被JVM执行。但是JVM确实和平台相关的,JVM会负责解析字节码,将其转化为计算机指令。所谓的跨平台是指字节码可以运行在各操作系统平台下的JVM,你可以上Oracle JAVA官方网站,可以看到各种不同平台下的JVM安装包,但是只需要编译一次字节码,就可以运行在这些JVM上。
Java源文件的编译、下载、解释和执行
Java应用程序的开发周期包括编译、下载、解释和执行几个部分。Java编译程序将Java源程序翻译为JVM可执行代码—字节码。这一编译 过程同C/C++的编译有些不同。当C编译器编译生成一个对象的代码时,该代码是为在某一特定硬件平台运行而产生的。因此,在编译过程中,编译程序通过查 表将所有对符号的引用转换为特定的内存偏移量,以保证程序运行。Java编译器却不将对变量和方法的引用编译为数值引用,也不确定程序执行过程中的内存布 局,而是将这些符号引用信息保留在字节码中,由解释器在运行过程中创立内存布局,然后再通过查表来确定一个方法所在的地址。这样就有效的保证了Java的 可移植性和安全性。 运行JVM字节码的工作是由解释器来完成的。解释执行过程分三部进行:代码的装入、代码的校验和代码的执行。装入代码的工作由"类装载器" (class loader)完成。类装载器负责装入运行一个程序需要的所有代码,这也包括程序代码中的类所继承的类和被其调用的类。当类装载器装入一个类时,该类被放 在自己的名字空间中。除了通过符号引用自己名字空间以外的类,类之间没有其他办法可以影响其他类。 在本台计算机上的所有类都在同一地址空间内,而所有从外部引进的类,都有一个自己独立的名字空间。这使得本地类通过共享相同的名字空间获得较高 的运行效率,同时又保证它们与从外部引进的类不会相互影响。当装入了运行程序需要的所有类后,解释器便可确定整个可执行程序的内存布局。解释器为符号引用 同特定的地址空间建立对应关系及查询表。通过在这一阶段确定代码的内存布局,Java很好地解决了由超类改变而使子类崩溃的问题,同时也防止了代码对地址 的非法访问。 随后,被装入的代码由字节码校验器进行检查。校验器可发现操作数栈溢出,非法数据类型转化等多种错误。通过校验后,代码便开始执行了。 Java字节码的执行有两种方式: 1.即时编译方式:解释器先将字节码编译成机器码,然后再执行该机器码。 2.解释执行方式:解释器通过每次解释并执行一小段代码来完成Java字节码程 序的所有操作。 通常采用的是第二种方法。由于JVM规格描述具有足够的灵活性,这使得将字节码翻译为机器代码的工作 具有较高的效率。对于那些对运行速度要求较高的应用程序,解释器可将Java字节码即时编译为机器码,从而很好地保证了Java代码的可移植性和高性能。 JVM规格描述 JVM的设计目标是提供一个基于抽象规格描述的计算机模型,为解释程序开发人员提很好的灵活性,同时也确保Java代码可在符合该规范的任何系 统上运行。JVM对其实现的某些方面给出了具体的定义,特别是对Java可执行代码,即字节码(Bytecode)的格式给出了明确的规格。这一规格包括 操作码和操作数的语法和数值、标识符的数值表示方式、以及Java类文件中的Java对象、常量缓冲池在JVM的存储映象。这些定义为JVM解释器开发人 员提供了所需的信息和开发环境。Java的设计者希望给开发人员以随心所欲使用Java的自由。 JVM定义了控制Java代码解释执行和具体实现的五种规格,它们是: • JVM指令系统 • JVM寄存器 • JVM栈结构 • JVM碎片回收堆 • JVM存储区 2.1JVM指令系统 JVM指令系统同其他计算机的指令系统极其相似。Java指令也是由 操作码和操作数两部分组成。操作码为8位二进制数,操作数进紧随在操作码的后面,其长度根据需要而不同。操作码用于指定一条指令操作的性质(在这里我们采 用汇编符号的形式进行说明),如iload表示从存储器中装入一个整数,anewarray表示为一个新数组分配空间,iand表示两个整数的" 与",ret用于流程控制,表示从对某一方法的调用中返回。 当长度大于8位时,操作数被分为两个以上字节存放。JVM采用了"big endian"的编码方式来处理这种情况,即高位bits存放在低字节中。这同 Motorola及其他的RISC CPU采用的编码方式是一致的,而与Intel采用的"little endian "的编码方式即低位bits存放在低位字节的方法不同。 Java指令系统是以Java语言的实现为目的设计的,其中包含了用于调用方法和监视多先程系统的指令。Java的8位操作码的长度使得JVM最多有256种指令,目前已使用了160多种操作码。 2.2JVM指令系统 所有的CPU均包含用于保存系统状态和处理器所需信息的寄存器组。如果虚拟机定义较多的寄存器,便可以从中得到更多的信息而不必对栈或内存进行 访问,这有利于提高运行速度。然而,如果虚拟机中的寄存器比实际CPU的寄存器多,在实现虚拟机时就会占用处理器大量的时间来用常规存储器模拟寄存器,这 反而会降低虚拟机的效率。针对这种情况,JVM只设置了4个最为常用的寄存器。它们是: • pc程序计数器 • optop操作数栈顶指针 • frame当前执行环境指针 vars指向当前执行环境中第一个局部变量的指针 所有寄存器均为32位。pc用于记录程序的执行。optop,frame和vars用于记录指向Java栈区的指针。 3JVM栈结构 作为基于栈结构的计算机,Java栈是JVM存储信息的主要方法。当JVM得到一个Java字节码应用程序后,便为该代码中一个类的每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息: • 局部变量 • 执行环境 • 操作数栈 局部变量用于存储一个类的方法中所用到的局部变量。vars寄存器指向该变量表中的第一个局部变量。 执行环境用于保存解释器对Java字节码进行解释过程中所需的信息。它们是:上次调用的方法、局部变量指针和操作数栈的栈顶和栈底指针。执行环 境是一个执行一个方法的控制中心。例如:如果解释器要执行iadd(整数加法),首先要从frame寄存器中找到当前执行环境,而后便从执行环境中找到操 作数栈,从栈顶弹出两个整数进行加法运算,最后将结果压入栈顶。 操作数栈用于存储运算所需操作数及运算的结果。 2.4JVM碎片回收堆 Java类的实例所需的存储空间是在堆上分配的。解释器具体承担为类实例分配空间的工作。解释器在为一个实例分配完存储空间后,便开始记录对该实例所占用的内存区域的使用。一旦对象使用完毕,便将其回收到堆中。 在Java语言中,除了new语句外没有其他方法为一对象申请和释放内存。对内存进行释放和回收的工作是由Java运行系统承担的。这允许 Java运行系统的设计者自己决定碎片回收的方法。在SUN公司开发的Java解释器和Hot Java环境中,碎片回收用后台线程的方式来执行。这不但为运行系统提供了良好的性能,而且使程序设计人员摆脱了自己控制内存使用的风险。 2.5JVM存储区 JVM有两类存储区:常量缓冲池和方法区。常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java方法的字节码。对于 这两种存储区域具体实现方式在JVM规格中没有明确规定。这使得Java应用程序的存储布局必须在运行过程中确定,依赖于具体平台的实现方式。 JVM是为Java字节码定义的一种独立于具体平台的规格描述,是Java平台独立性的基础。目前的JVM还存在一些限制和不足,有待于进一步的完善,但无论如何,JVM的思想是成功的。 对比分析:如果把Java原程序想象成我们的C++原程序,Java原程序编译后生成的字节码就相当于C++原程序编译后的80x86的机器码 (二进制程序文件),JVM虚拟机相当于80x86计算机系统,Java解释器相当于80x86CPU。在80x86CPU上运行的是机器码,在Java 解释器上运行的是Java字节码。 Java解释器相当于运行Java字节码的“CPU”,但该“CPU”不是通过硬件实现的,而是用软件实现的。Java解释器实际上就是特定的 平台下的一个应用程序。只要实现了特定平台下的解释器程序,Java字节码就能通过解释器程序在该平台下运行,这是Java跨平台的根本。当前,并不是在 所有的平台下都有相应Java解释器程序,这也是Java并不能在所有的平台下都能运行的原因,它只能在已实现了Java解释器程序的平台下运行。Scala
Groovy Clojure Kotlin Xtend大部分人大谈特谈JAVA语言,这对于我来说也许听起来很奇怪,但是我无法不去在意。JVM才是Java生态系统的核心啊。
“我真正关心的是Java虚拟机的概念,因为是它把所有的东西都联系在了一起;是它造就了Java语言;是它使得事物能在所有的异构平台上得到运行;也还是它使得所有类型的语言能够共存。”
(James Gosling, Java编程语言的创造者 (2011, TheServerSide))
JVM最初是为了支持java编程语言。然而,随着时间的流逝,越来越多的语言被改编或设计运行在JVM上。除了java语言,比较知名的JVM上的编程语言还有:
“Groovy有超过Java将能够提供的甜点,例如它具有轻易地在宿主程序中嵌入并编译,以提供定制业务规则的能力,还有它如何为领域特定语言(Domain-Specific Language)提供优雅,简洁并且可读性好的语法的能力.” ( Guillaume Laforge, Groovy的项目带头人)
动态类型和脚本语言(尽管一开始是一种动态语言,但在其 2012年的2.0发行版中也开始加入编译时的静态类型检查了),Groovy的闭包(Closure)是很好的。Groovy使得运行时的元编程、编译时的元编程、动态类型以及静态类型容易处理。
关键词: DSL,Grails,Gradle
“意在使其端正,而不塞入太多的语言特性到其里面,我在Scala上专注于使它变得更加的简单.那是人们常常有的一种误解,他们认为Scala是一种带有许许多多特性的宏大语言.尽管这通常不是真的.它实际上是一个相当小的语言——当Java8面世之时它将比Java更加的小巧。” (Martin Odersky, Scala 创始人)
使用类型推断混合了面向对象编程(OOP)和函数式编程(FP)的一种静态类型编程语言。这意味着Scala程序能够被编写成许多完全不同的风格——纯函数式风格的,不纯函数式的,或混合式风格。
关键词:FP,类型系统
“我们认为Kotlin的定位是一种现代化工业语言:它专注于代码重用和可读性的弹性抽象,以及面向早期错误侦测,和明确捕获维护与清理的意图,这些问题的静态类型安全性。Kotlin最重要的使用场景之一是对于一个庞大的Java代码库,其开发者需要一个更棒的语言:你能够将Java和Kotlin自由混合,迁移可以是渐进式的,不需要一下子对整个代码库进行改变。” (Andrey Breslav, Kotlin创始人)
静态类型的语言, 由IntelliJ IDEA团队JetBrains开发。
使用Kotin可以写出一些非常优雅的代码。举个复合函数的例子[6]:
/** * The composition function return a composition of two functions passed to it: * compose(f, g) = f(g(*)). * Now, you can apply it to callable references. */fun main(args: Array) { val oddLength = compose(::isOdd, ::length) val strings = listOf("a", "ab", "abc") println(strings.filter(oddLength))}fun isOdd(x: Int) = x % 2 != 0fun length(s: String) = s.lengthfun compose(f: (B) -> C, g: (A) -> B): (A) -> C { return { x -> f(g(x)) }}
关键词:IDEA,优雅
“我着手创建一种语言,意在应对我在使用Java和C#编写的一些类型的应用程序——像广播自动化、调度以及选举系统之类那些东西——它们许多都需要解决的并发问题.我发现只用面向对象编程和用那些语言的并发方法,对于处理这些类型的问题并不怎么够好——它们太难了。我是List的拥护者,还有其它的函数式语言,而我想要做的就是解决那些问题,创造一种立足于实际的语言,再也不用拿Java来编程了.” (Rich Hickey, Clojure创始人在2009年InfoQ访谈)
动态类型语言,Lisp方言。Clojure是一种非常类似于Lisp和Scheme的函数式编程语言.函数式范式同那些习惯于Java的面向对象方式并且习惯于其副作用的方式非常不同。
关键词: Lisp,FP
JVM语言时间轴概览[5]:
使用哪种语言,完全依赖程序员的性情了。但是在项目,工作中“应该使用”哪种语言,往往会有诸多限制。
RebelLabs《Java工具和技术概览2014》[4]的报告上“要去学习的下一个JVM语言”:
RebelLabs《Java工具和技术概览2016》[4]的报告上的“你最常使用的JVM上的语言”:
一个完整的语言有:
前端、优化、后端、runtime、库
JVM生态体系,把后面四个都给包办了。
jvm(Java虚拟机),是用C写的,跟操作系统打交道C/C++目前看来,是好的选择。虚拟机就是java与操作系统的中间层。
库/API就基本是java自身封装实现。
从最初的Jython和JRuby,到Scala,Clojure都是在JVM上实现的语言。为什么它们选择JVM?
你的语言编译器后端只需要输出 JVM 字节码就可以。跨平台需要极大的工作量(这个轮子,造起来有点耗时耗力)
JIT 可以在运行中记录程序运行的特征,并在其基础上做大量的优化(Java 企业级应用的优秀性能很大程度上是由此而来)。 JIT 自从 HotSpot JVM 随 Java 1.2 发布以来,JVM JIT 的性能不断提高,是无可争议的成功产品。把 JVM 作为目标平台意味着大量的性能优化工作可以「外包」给 JVM 来做,大大缩减了 Guest 语言的开发预算。
JVM 作为一个成熟的高层运行环境,为 Guest 语言提供了很多运行时所需要的服务,比如内存管理(有业界领先的垃圾回收等),很大程度上避免了额外的独立开发。
JVM 有多个独立实现,也有若干厂商会持续推进,资料完备,社区巨大。
Java 社区有大量成熟的库,一般来说,运行在 JVM 上的其它语言都会设计一个专用的「桥」来帮助直接使用 Java 的库。 Java 有成熟的开发工具和环境。另外一个趋势是把 Javascript 成为新的目标平台。很多主流语言都已经出现了编译器可以翻译成 Javascript,这也是得益于近年来 Javascript 虚拟机性能的显著提升。
List-of-languages-that-compile-to-JS:
下一代普遍可接受语言(next mass-appeal language)中,人的因素应该起到重要作用。
NBJL可以走多远就目前来看是难以下定论的,但是我相信这是一个比较实际的问题。我们所需要的新的程序设计语言能够不需要大规模的培训,程序员们可以快速上手。
在其功能方面,注诸如如下条目:
当然还有其他一些可以讨论的主题-语言设计其实堪比艺术品设计,有太多角度可以观察了。
1.
2. 3. 4. 5. 6. 7.转载地址:http://oyuta.baihongyu.com/