2010年秋,Java SE执行委员会商议决定执行B计划。这个决定是尽快发布Java 7,并把一些主要特性延迟到Java 8中。这一结论是在对社区进行广泛征询和投票后得出的。
在Java 7中发起的某些特性已经被推到Java 8中了,还有些特性已经缩减了范围以便为将来的特性打下基础。在这一节中,我们会对一些期望Java 8突出的特性做简要介绍,包括那些被延迟的特性。在这一阶段,没有什么是板上钉钉的,特别是语法。所有示例代码都只是初步构想,可能跟Java 8的最终写法差别很大。欢迎来到风口浪尖!
14.1.1 lambda表达式(闭包)
将在Java 8中构建的Java 7特性中最有代表性的是MethodHandles
和invokedynamic
。它们本身是非常实用的特性(在Java 7中,invokedynamic
主要用在语言和框架实现上)。
在Java 8中,这些特性是将lambda表达式引入Java语言的基础。可以这样理解,lambda表达式跟前面在备选语言中讲的函数字面值类似,它们也能用来解决我们在前面重点强调的那类问题。
就Java 8的语法而言,还需要决定lambda表达式在代码中如何表示。但其基本特性已经确定下来了,所以我们先来看看基本的Java 8语法,如代码清单14-1所示。
代码清单14-1 在Java中用lambda表达式实现Schwartzian变换
public List<T> schwarz(List<T> x, Mapper<T, V> f) { return x.map(w -> new Pair<T,V>(w, f.map(w))) .sorted((l,r) -> l.hashed.compareTo(r.hashed)) .map(l -> l.orig).into(new ArrayList<T>);}
schwartz
方法看起来应该眼熟,它是在10.3节中用闭包实现的Schwartzian变换。代码清单14-1展示了Java 8中lambda表达式的下列基本语法:
- 在lambda表达式前部有个参数列表;
- 组成lambda表达式的主体代码块用括号括起来;
- 用箭头(->)来分隔参数列表和lambda表达式的主体;
- 参数列表中参数的类型是可推断的。
第9章中Scala的函数字面值和这个写法很像,所以这种语法应该不会让你觉得特别陌生。代码清单14-1中的lambda表达式非常短,全都只有一行。实际上,lambda表达式是可以包含多行代码的,其主体甚至可以很大。经过初步分析,那些适于改造成lambda表达式的代码改造后的长度都应该在1到5行之间。
代码清单14-1中还介绍了另外一个新特性。变量x
的类型是List<T>
。我们在x
上调用了方法map
。map
方法接受了一个lambda表达式作为其参数。停!List
接口根本就没有map
方法,并且在Java 7及之前都不存在lambda表达式。
我们来仔细看看这个问题是如何解决的。
1. 扩展和默认方法
我们所面临的问题本质是:怎么向已有接口中添加方法以使其“lambda化”而又不破坏其向后兼容性?
答案来自于Java的一个新特性:扩展方法。它可以为没有提供扩展方法的接口实现提供一个可用的默认方法。
这些默认的方法实现必须在接口本身内部定义。比如跟List
搭配的AbstractList
,跟Map
搭配的AbstractMap
,跟Queue
搭配的AbstractQueue
。这些类是为各自的接口存放新的扩展方法默认实现的理想之所。Java内置的集合类是扩展方法和lambda化的主要应用场景,但看起来这种模型在最终用户代码中也适用。
Java怎么实现扩展方法
扩展方法会在类加载时进行处理。当加载一个带有扩展方法的接口实现时,类加载器会检查它是否实现了自己的扩展方法。如果没有,类加载器会定位到默认方法,并把一个桥接方法插入新加载类的字节码中。这个桥接方法会用
invokedynamic
调用默认方法。
扩展方法无需打破向后兼容性就可以让发布后的接口得到进化。开发人员可以借此带着lambda表达式为老旧的API注入新的活力。但lambda表达式对于JVM来说是什么呢?是对象吗?如果是,它们的类型是什么?
2. SAM转换
lambda表达式提供了一种紧凑的办法,可以声明少量内联代码并将其作为数据传递。也就是说lambda是一个对象,就像我们在本书第三部分中对lambda表达式和函数字面值的解释一样。具体说来,你可以把lambda表达式当做Object
的一个子类,它没有参数(因此也没有状态),只有一个方法。
还有一种理解这个问题的方式:通过术语SAM(Single Abstract Method,单例抽象方法)。SAM的概念在各种Java API中都有体现,是一种常见主题。很多API中都有只声明了一个单例方法的接口。 Runnable
、Comparable
、Callable
和ActionListener
之类的监听器都只声明名了一个方法,因此都算SAM类。
在刚开始用lambda表达式时,可以把它们当做语法糖——为给定接口编写匿名实现的简便写法。过一段时间后,你可以掌握更多的函数式技术,甚至可能会从Scala或Clojure中把自己喜欢的技巧引入Java代码中。学习函数式编程是个循序渐进的过程:从集合的映射、排序和过滤技术开始学起,然后慢慢向外开疆拓土。
现在让我们进入下一个大主题:模块化编程,它正在Jigsaw项目的支持下如火如荼地展开。
14.1.2 模块化(拼图Jigsaw)
处理classpath有时毫无疑问是不太理想的。围绕着JAR文件和classpath构建的生态系统有些众所周知的问题:
- JRE自身的规模就比较大;
- JAR文件提倡整体式部署模型;
- 有些繁琐且极少会用到的类仍然必须加载;
- 启动慢;
- classpath是脆弱的野兽,并且跟机器上的文件系统结合得过于紧密;
- classpath基本上是一个扁平化的命名空间;
- JAR不具有固有的版本;
- 即便逻辑上没有关联的类之间也有复杂的相互依赖关系。
要解决这些问题,需要一个新的模块系统。但要先解决架构上的问题。其中最重要的如图14-1所示。
图14-1 模块化系统的架构选择
我们是应该引导VM然后再使用用户层模块化系统(比如OSGi),还是应该彻底迁移到模块化平台上?
后一种方式需要启动一个能支持模块的最基本的 /"内核/" VM,然后根据启动应用程序的需要添加特定的模块。这要求对VM,以及JRE中很多现有的类做颠覆性的修改,但潜在收益更大。
JVM应用程序的启动时间可以跟shell和脚本语言相媲美。
能显著降低应用程序部署的复杂性。
针对性的Java安装所占用的资源可以显著减少(对硬盘、内存和安全性都有积极影响)。如果你不需要CORBA或RMI,就不用装!
可以以更加灵活的方式升级Java安装。如果在Collections中发现了一个严重的bug,只有那个模块需要升级。
撰写本书时看起来Jigsaw项目会选择第二种方式。但在它发布并能投入使用之前,还有很长的路要走。下面是一些仍在讨论的重要问题:
- 平台或应用的正确发布单元是什么?
- 是不是需要一种跟包和JAR都不同的新结构?
这一设计决策的影响极为重要:Java**无处不在**,因而这种模块化的设计要渗透到所有地方。它也要支持跨OS平台。
Java平台最起码要能在Linux、Solaris、Windows、Mac OS X、BSD Unix和AIX上部署模块化应用。这些平台中有些有需要Java模块集成的包管理器(比如Debian的apt、Red Hat的rpm,以及Solaris包)。而其他平台,比如Windows,没有可供Java使用的包管理系统。
这一设计还有其他的限制。不过这一领域已经有一些成熟的项目了:比如Maven和Ivy这样的依赖项管理系统,还有发起倡议的OSGi。新的模块化系统应该尽一切可能跟现有项目集成,即便在完全的集成和兼容被证明不可能之后,也应该提供一个顺畅的升级途径。
不管将来会怎样,Java 8的发布应该会给Java应用程序的交付和部署带来革命性的变化。
我们去看看JDK 8应该给JVM的其他公民带来的一些特性,包括我们在前面研究的那些语言。