Java所获得的巨大成功证明了这种设计方式是正确的,但如果这依然是当今Java的重要目标的话,那么其结果就是语言的演进将变得非常缓慢。除此以外,Java是一门成熟、使用广泛的语言这个事实也将导致其演进过程充满了困难。
一方面,添加到语言中的每个特性都可能在一定程度上造成不可预料的结果,这么做会疏远那些已经使用了该语言的开发者。另一方面,本身很***的特性可能在同语言中的其他特性进行交互时产生不可预料的结果。更糟的是,一旦增加了某个语言特性,几乎就不可能再将其移除了,即便是该特性会对整个语言产生不利影响也没办法。为了证明某个新特性是正确的,语言设计者必须确信从长远来看,该特性会给语言带来好处,而不是短期效益或是针对某个问题的快速解决方案,之后就变得可有可无了。
为了降低风险,语言设计者通常都会创建单独的一种语言或是分支来进行试验,比如Pizza语言就是在实现前用来测试Java泛型的。这种方式的问题在于试验的参与者非常小众并且都是自己想参与进来的;显然他们对语言特性很感兴趣,很多人都是学者或研究员。但是,在普通的程序员开始使用这些特性时,那些学者或是研究员认为很棒的特性可能会变得很糟。
为了直观感受一下这种情况,请考虑关于Java 7闭包特性的激烈争论。一段时间以来,有人在提案中给出了闭包的实现,但最终却还是没有达成共识。随后,Sun决定不打算在JDK 7中添加完整的闭包支持。这时争论的焦点转向为Java是否变得越来越复杂了,在Java 5中添加泛型(尤其是通配符语法)时就已经出现了这种争论;在Java已经通过匿名内部类部分实现该功能的情况下,完整的闭包支持是否是正确的呢。需要完 整闭包支持的两个重要场景是简化fork/join API(添加到了JDK 7中以改进多核编程)的使用以及辅助资源的清理。Josh Bloch的ARM block提案(期望通过Project Coin加入到JDK 7中)就第二个问题给出了另一种解决方案。Cliff Click博士在面向Java的可扩展、非阻塞编程风格的研究中给出了关于fork/join的另一种方案,随着处理器核心数的不断增 加,这种方案看起来更合理。如果这一切都成为可能的话,那么Java中使用闭包的地方将变得非常少了,语言根本没必要提供这个特性。
话虽如此,但对于编程语言来说,持续不断地平稳发展还是非常重要的。因此本文探究了如下3种技术以向Java中增加新的语言特性而又不改变语言本身,他们 是客户化领域特定语言(DSL)、Java 6的注解处理器(通过库来增加可选的语言特性)以及将语法糖从语言迁移到IDE中。每项技术都可以让众多的主流开发者以非侵入的方式体验这些新特性,最棒 的想法则可以融入到语言核心当中。
客户化DSL
在这3项技术中,人们谈论最多的还是DSL。该术语的确切含义至今尚未统一,但出于讨论的目的,我们在这里简单地把它看作是用于解决特定问题、应用范围很窄 的一种语言而非用于解决所有计算问题的通用语言。这样,DSL就并非是图灵完备(non-Turing complete)的。当然了,还是会有一些边际情况存在的,比如说Postscript是一种图灵完备的语言, 但根据我们方才的定义,它也是一种DSL。
如上所述,DSL并非新概念。其他类似的DSL还有正则表达式、XSLT、Ant以及JSP等等,所有这些都需要某种客户化的解析器对其进行处理。Martin Fowler还说fluent interfaces/API也可以看作是另一种DSL,称之为内部DSL。他说内部DSL是直接在宿主语言中开发出来的。这对于Lisp和 Smalltalk开发者来说很容易理解,而最近Ruby社区也开始对内部DSL情有独钟了。
虽然很多知名的DSL都是由商业公司开发和维护的,但一些企业开发团队也已经使用该技术来创建能够快速解决其问题的语言了,但毕竟还是小众,这可能是 DSL领域门槛比较高的缘故吧。负责DSL的团队必须要设计语言、构建解析器和其他工具来支持开发团队,还要对每个新员工进行培训,让其了解DSL的工作 机理。这时,涌现出了能够支援DSL开发的工具,这极大地改变了当前的状况。Intentional Software所开发的Intentional Domain Workbench比Java还要久远,它首度实现了该工具的功能。
该项目创建于微软研究院,Charles Simonyi博士在1995年所发表的论文“The Death of Computer Languages, the Birth of Intentional Programming”描绘了其愿景。2002年,Simonyi创建了Intentional Software以继续实现其想法,大家可以看看介绍该系统的视频,***震撼力。目前该产品的版本是1.0,但只有极少数的合作者能够访问。
其他一些软件公司也在研究这个概念,其中就包括以IntelliJ IDEA Java IDE而扬名天下的JetBrains,JetBrains最近发布了Meta Programming System(MPS)1.0版。MPS并没有使用解析器而是直接使用了Abstract Syntax Tree(AST)。它提供了一个类似于文本的投影编辑器(projectional editor)以便程序员能够操纵AST,同时该编辑器也可用于编写语言和程序。当程序员使用投影时就会为树上的每个节点创建一个文本投影,这样变换就会 反映到节点当中。开发者可以通过这种方式以任意组合(通常称之为语言组合)扩展和嵌入语言。JetBrains正在内部使用该产品,最近发布的bug追踪 产品YouTrack就是使用该系统开发的。 #p#
Java 6注解处理器
相对于Ruby、Smalltalk和Lisp来说,DSL在很多主流语言(如Java)中的流行程度就稍逊一筹了,但最近Java语言的一些变化(尤其 是Java 6中新增的注解处理器)为开发者提供了新的机遇以在其中使用DSL。对于Java EE 6中的JPA 2.0来说,其某些API本身就是 DSL。注解处理器会为应用中的每个持久化类建立一个元模型类型(metamodel type)。虽然开发者可以手工处理Java中的元模型,但这实在太无聊而且极易出错。注解处理器的出现改变了这一切,因为它内建于Java 6,因此无需特殊的IDE支持——IDE会代理编译器所触发的注解处理器,之后会自动生成元数据模型。
程序库也可以通过注解处理器来提供新的语言特性。比如说,Bruce Chapman的原型“no closures”提案就凭借该技术将方法转换为 Single Abstract Method(SAM)类型,然后在Java 6上编译。在与其交谈过程中,Chapman指出SAM类型还支持自由变量(free variable),这是闭包的一个关键技术,除了Single Abstract Method所需的参数外,方法体还可以通过@As.Additional注解声明额外的参数。在获得SAM类型的实例时,这些参数可以带有绑定值,然后在每次调用时传递给方法。
Chapman还创建了Rapt项目以探索该技术的其他使用场景,同时为语言的两个变化提供了自己的实现——多行字符串(Multiline Strings)与XML字面值(XML literals)——这两个特性是 为JDK 7准备的,但却不会包含到最终的发布中。Java甚至也可以使用这种方法实现闭包,Chapman对此说到,“我们刚刚使用该技术完成了一个Swing项目,在这个过程中发现了泛型的一些小bug,其中一个bug还没有修复,除此之外一切都很棒,没人再想使用传统 的匿名内部类了。”
另一个探究注解处理器的项目是Lombok,它将该技术又向前推进了一大步。Lombok将注解作为回调以运行Java agent,后者会根据注解重写各种javac内核。由于操纵的是内部类,因此它不太适合于产品使用(JVM各个小版本中的内部类也可能不一样),但该项目对于注解处理器到底能做什么这个问题上还是颇具启发意义的,包括:通过@Getter和@Setter注解定义各种访问级别的属性,如@Setter(AccessLevel.PROTECTED) private String name:
@EqualsAndHashCode注解会根据对象中的属性实现hashCode()和 equals()方法;
@ToString注解会实现toString()方法;
@data方法相当于@ToString、@EqualsAndHashCode、所有属性的@Getter以及所有非final属性的@Setter的集合,可以使用 @data方法和构造方法初始化final属性。
还可以通过这种方式进行其他的语言试验,比如移除Java中的非运行时异常等。虽然注解处理器技术为语言试验开辟了一条新航线,但还是要注意生成代码的可读性,保证开发者能读懂生成的代码。Chapman给出了很多建议:
要生成源代码而不是字节码,注意生成代码的格式(尤其是缩进)。编译器不在乎生成的代码是不是都在同一行,但用户在乎。我甚至还使用注解处理器在恰当的地方增加了一些注释和javadoc。如果这项技术逐渐流行起来,用户将可以通过IDE查看编译期所生成的代码。
IDE中的语法糖
Bruce Chapman还提到了第三项技术——将语法糖从语言迁移到IDE中——他在博客中对该问题做过深入阐述。对于Java IDE来说,生成部分样板代码已成为不可或缺的功能了,比如类的getters和setters,但IDE开发者刚刚开始深挖该这个概念。 JetBrains的IntelliJ 9为内部类提供了一个类似于闭包的简洁的代码块语法,开 发者也可以自己加。
就像代码折叠一样,该语法会扩展到编译器能够处理的完整的匿名内部类——这样坚持使用标准的匿名内部类语法的开发者很容易就适应了。 Eclipse也有一个类似的插件。关键在于这种语法仅仅是实际代码的另一种展示方式而已,编译器和任何源代码管理工具都能够像以前那样处 理他们。开发者可以在两种方式间自由切换(就像代码的展开与收起一样),无权访问该语法糖定义的人仅仅会看到正常的Java代码。Chapman说到:
要想实现易于使用的目标还有很多工作要做,但从长远来看,我发现开发者会很轻松地实现加糖/脱糖(sugaring/desugaring)的转换(可以 参考jackpot来 了解实现原理)、不断尝试、不断演进并与同事和社区分享好的点子。这么做的好处与语言的演进别无二致。***的东西会流行起来并形成实际语言演进的基础,如 果必要的话还可以随时去除该方法无法实现的“噪音”。
由于语法糖还要兼顾(非常多)其他的语言特性,因此无法提供完整的闭包支持;比如说BGGA闭包就有一些特性无法匹配匿名内部类,因此不能通过这种方式实现。话虽如此,这种想 法却展示了通过各种新语法来表示匿名内部类的可行性,类似于BGGA语法或是FCM语法,开发者也可以选择自己喜欢的语法。其他的语言特性(如null-safe Elvis operator)可以通过这种方式实现。要想进一步验证该想法,可以体验一下这个NetBeans module(由Chapman开发),这正是他所说的用于 Properties的原型。
结论
在语言的发展过程中总是需要考虑稳定与发展之间的平衡。上面介绍的技术所带来的好处是他们不会影响平台或是语言本身。这么做允许我们犯错误,也有益于进行快速激进的试验。由于开发者能够自由地进行试验,我们看到越来越多的人开始解决常见的样板代码“噪音”问题,如匿名内部类语法等,同时将这些想法以合理的方式组织起来以获得***的价值。我们将欣喜地看到开发者使用这些方法将Java平台推向新的远方。
【编辑推荐】