当前位置: 首页 > 新闻 > 信息荟萃
编号:71
java程序员修炼之道.pdf
http://www.100md.com 2019年12月17日
第1页
第6页
第19页
第26页
第34页
第375页

    参见附件(5888KB,518页)。

     java程序员修炼之道是由Benjamin J. Evans /Martijn Verburg联合所著,全书分为四个部分,分别探讨了java的新特性和相关知识技术,结合大量实际案例。

    java程序员修炼之道预览图

    Java的优点构造能力

    运行时环境的自动管理(比如垃圾收集、即时编译);

    语法简单,核心概念相对较少;

    保守的语言进化方式;

    在类库中增加功能和复杂性;

    广泛、开放的生态系统。

    java程序员修炼之道阅读须知

    本书内容大体上适合顺序阅读,但我们也能理解某些读者想直奔主题的心情,因此也在一定程度上迎合了这种阅读需求。

    我们非常认同自己动手的学习方法,所以建议读者在阅读的同时尝试示例代码。

    java程序员修炼之道四部分介绍

    第一部分共两章,都是关于Java 7的内容。本书通篇使用Java 7的语法和语义,所以第1章“初 识Java 7”是必读的。那些要处理文件、文件系统和网络I/O的开发人员应该会对第2章“新I/O”特别感兴趣。

    第二部分共四章(第3~6章),涉及的主题包括依赖注入、现代并发、类文件/字节码以及性能调优。

    第三部分共四章(第7~10章)介绍了JVM上的多语言编程。第7章是必读的,因为这一章介绍的JVM上可用语言的类型和使用是阅读后面章节的基础。接下来的三章分别介绍与Java类似的语言Groovy、兼具OO和函数式特色的混合语言Scala和纯函数式语言Clojure。刚接触函数式编程的开发人员可能需要按顺序阅读,但这几章本身是相互独立的,可以跳着读。

    第四部分(最后四章)在之前内容基础上介绍了新内容。虽然各章可以独立阅读,但是在某些部分我们会假定你已经读过之前的内容,或者已经熟悉那些主题。

    java程序员修炼之道截图

    更多电子书下载就在www.aiyadu.com

    书名:Java程序员修炼之道

    作者:Benjamin J. Evans, Martijn Verburg

    译者:吴海星

    ISBN:978-7-115-32195-4

    本书由北京图灵文化发展有限公司发行数字版。版权所有,侵权必究。

    您购买的图灵电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

    我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

    如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可

    能追究法律责任。

    更多电子书下载就在www.aiyadu.com目录

    版权声明

    序

    前言

    致谢

    关于本书

    阅读须知

    读者对象

    路线图

    代码约定及下载

    软件需求

    作者在线

    关于作者

    关于封面图片

    第一部分 用Java 7做开发

    第1章 初识Java 7

    1.1 语言与平台

    1.2 Coin项目:浓缩的都是精华

    1.3 Coin项目中的修改

    1.3.1 switch语句中的String

    1.3.2 更强的数值文本表示法

    1.3.3 改善后的异常处理

    1.3.4 Try-with-resources(TWR)

    1.3.5 钻石语法

    1.3.6 简化变参方法调用

    1.4 小结

    第2章 新IO

    2.1 Java IO简史

    2.1.1 Java 1.0到1.3

    2.1.2 在Java 1.4中引入的NIO

    2.1.3 下一代IO-NIO.2

    2.2 文件IO的基石:Path

    2.2.1 创建一个Path

    2.2.2 从Path中获取信息

    2.2.3 移除冗余项

    2.2.4 转换Path

    2.2.5 NIO.2 Path和Java已有的File类

    2.3 处理目录和目录树

    2.3.1 在目录中查找文件

    2.3.2 遍历目录树

    2.4 NIO.2的文件系统IO

    更多电子书下载就在www.aiyadu.com2.4.1 创建和删除文件

    2.4.2 文件的复制和移动

    2.4.3 文件的属性

    2.4.4 快速读写数据

    2.4.5 文件修改通知

    2.4.6 SeekableByteChannel

    2.5 异步 IO操作

    2.5.1 将来式

    2.5.2 回调式

    2.6 Socket和Channel的整合

    2.6.1 NetworkChannel

    2.6.2 MulticastChannel

    2.7 小结

    第二部分 关键技术

    第3章 依赖注入

    3.1 知识注入:理解IoC和DI

    3.1.1 控制反转

    3.1.2 依赖注入

    3.1.3 转成DI

    3.2 Java中标准化的DI

    3.2.1 @Inject注解

    3.2.2 @Qualifier注解

    3.2.3 @Named注解

    3.2.4 @Scope注解

    3.2.5 @Singleton注解

    3.2.6 接口Provider

    3.3 Java中的DI参考实现:Guice 3

    3.3.1 Guice新手指南

    3.3.2 水手绳结:Guice的各种绑定

    3.3.3 在Guice中限定注入对象的生命周期

    3.4 小结

    第4章 现代并发

    4.1 并发理论简介

    4.1.1 解释Java线程模型

    4.1.2 设计理念

    4.1.3 这些原则如何以及为何会相互冲突

    4.1.4 系统开销之源

    4.1.5 一个事务处理的例子

    4.2 块结构并发(Java 5之前)

    4.2.1 同步与锁

    4.2.2 线程的状态模型

    4.2.3 完全同步对象

    4.2.4 死锁

    更多电子书下载就在www.aiyadu.com4.2.5 为什么是synchronized

    4.2.6 关键字volatile

    4.2.7 不可变性

    4.3 现代并发应用程序的构件

    4.3.1 原子类:java.util.concurrent.atomic

    4.3.2 线程锁:java.util.concurrent.locks

    4.3.3 CountDownLatch

    4.3.4 ConcurrentHashMap

    4.3.5 CopyOnWriteArrayList

    4.3.6 Queue

    4.4 控制执行

    4.4.1 任务建模

    4.4.2 ScheduledThreadPoolExecutor

    4.5 分支合并框架

    4.5.1 一个简单的分支合并例子

    4.5.2 ForkJoinTask与工作窃取

    4.5.3 并行问题

    4.6 Java内存模型

    4.7 小结

    第5章 类文件与字节码

    5.1 类加载和类对象

    5.1.1 加载和连接:概览

    5.1.2 验证

    5.1.3 Class对象

    5.1.4 类加载器

    5.1.5 示例:依赖注入中的类加载器

    5.2 使用方法句柄

    5.2.1 MethodHandle

    5.2.2 MethodType

    5.2.3 查找方法句柄

    5.2.4 示例:反射、代理与方法句柄

    5.2.5 为什么选择MethodHandle

    5.3 检查类文件

    5.3.1 介绍javap

    5.3.2 方法签名的内部形式

    5.3.3 常量池

    5.4 字节码

    5.4.1 示例:反编译类

    5.4.2 运行时环境

    5.4.3 操作码介绍

    5.4.4 加载和储存操作码

    5.4.5 数学运算操作码

    5.4.6 执行控制操作码

    更多电子书下载就在www.aiyadu.com5.4.7 调用操作码

    5.4.8 平台操作操作码

    5.4.9 操作码的快捷形式

    5.4.10 示例:字符串拼接

    5.5 Invokedynamic

    5.5.1 invokedynamic如何工作

    5.5.2 示例:反编译invokedynamic调用

    5.6 小结

    第6章 理解性能调优

    6.1 性能术语

    6.1.1 等待时间

    6.1.2 吞吐量

    6.1.3 利用率

    6.1.4 效率

    6.1.5 容量

    6.1.6 扩展性

    6.1.7 退化

    6.2 务实的性能分析法

    6.2.1 知道你在测量什么

    6.2.2 知道怎么测量

    6.2.3 知道性能目标是什么

    6.2.4 知道什么时候停止优化

    6.2.5 知道高性能的成本

    6.2.6 知道过早优化的危险

    6.3 哪里出错了?我们担心的原因

    6.3.1 过去和未来的性能趋势:摩尔定律

    6.3.2 理解内存延迟层级

    6.3.3 为什么Java性能调优存在困难

    6.4 一个来自于硬件的时间问题

    6.4.1 硬件时钟

    6.4.2 麻烦的nanoTime

    6.4.3 时间在性能调优中的作用

    6.4.4 案例研究:理解缓存未命中

    6.5 垃圾收集

    6.5.1 基本算法

    6.5.2 标记和清除

    6.5.3 jmap

    6.5.4 与GC相关的JVM参数

    6.5.5 读懂GC日志

    6.5.6 用VisualVM查看内存使用情况

    6.5.7 逸出分析

    6.5.8 并发标记清除

    6.5.9 新的收集器:G1

    更多电子书下载就在www.aiyadu.com6.6 HotSpot的JIT编译

    6.6.1 介绍HotSpot

    6.6.2 内联方法

    6.6.3 动态编译和独占调用

    6.6.4 读懂编译日志

    6.7 小结

    第三部分 JVM上的多语言编程

    第7章 备选JVM语言

    7.1 Java 太笨?纯粹诽谤

    7.1.1 整合系统

    7.1.2 函数式编程的基本原理

    7.1.3 映射与过滤器

    7.2 语言生态学

    7.2.1 解释型与编译型语言

    7.2.2 动态与静态类型

    7.2.3 命令式与函数式语言

    7.2.4 重新实现的语言与原生语言

    7.3 JVM上的多语言编程

    7.3.1 为什么要用非Java语言

    7.3.2 崭露头角的语言新星

    7.4 如何挑选称心的非Java语言

    7.4.1 低风险

    7.4.2 与Java的交互操作

    7.4.3 良好的工具和测试支持

    7.4.4 备选语言学习难度

    7.4.5 使用备选语言的开发者

    7.5 JVM对备选语言的支持

    7.5.1 非Java语言的运行时环境

    7.5.2 编译器小说

    7.6 小结

    第8章 Groovy:Java的动态伴侣

    8.1 Groovy入门

    8.1.1 编译和运行

    8.1.2 Groovy控制台

    8.2 Groovy 101:语法和语义

    8.2.1 默认导入

    8.2.2 数字处理

    8.2.3 变量、动态与静态类型、作用域

    8.2.4 列表和映射语法

    8.3 与Java的差异——新手陷阱

    8.3.1 可选的分号和返回语句

    8.3.2 可选的参数括号

    8.3.3 访问限定符

    更多电子书下载就在www.aiyadu.com8.3.4 异常处理

    8.3.5 Groovy中的相等

    8.3.6 内部类

    8.4 Java不具备的Groovy特性

    8.4.1 GroovyBean

    8.4.2 安全解引用操作符

    8.4.3 猫王操作符

    8.4.4 增强型字符串

    8.4.5 函数字面值

    8.4.6 内置的集合操作

    8.4.7 对正则表达式的内置支持

    8.4.8 简单的XML处理

    8.5 Groovy与Java的合作

    8.5.1 从Groovy调用Java

    8.5.2 从Java调用Groovy

    8.6 小结

    第9章 Scala:简约而不简单

    9.1 走马观花Scala

    9.1.1 简约的Scala

    9.1.2 match表达式

    9.1.3 case类

    9.1.4 actor

    9.2 Scala能用在我的项目中吗

    9.2.1 Scala和Java的比较

    9.2.2 何时以及如何开始使用Scala

    9.2.3 Scala可能不适合当前项目的迹象

    9.3 让代码因Scala重新绽放

    9.3.1 使用编译器和REPL

    9.3.2 类型推断

    9.3.3 方法

    9.3.4 导入

    9.3.5 循环和控制结构

    9.3.6 Scala的函数式编程

    9.4 Scala对象模型:相似但不同

    9.4.1 一切皆对象

    9.4.2 构造方法

    9.4.3 特质

    9.4.4 单例和伴生对象

    9.4.5 case类和match表达式

    9.4.6 警世寓言

    9.5 数据结构和集合

    9.5.1 List

    9.5.2 Map

    更多电子书下载就在www.aiyadu.com9.5.3 泛型

    9.6 actor介绍

    9.6.1 代码大舞台

    9.6.2 用mailbox跟actor通信

    9.7 小结

    第10章 Clojure:更安全地编程

    10.1 Clojure介绍

    10.1.1 Clojure的Hello World

    10.1.2 REPL入门

    10.1.3 犯了错误

    10.1.4 学着去爱括号

    10.2 寻找Clojure:语法和语义

    10.2.1 特殊形式新手营

    10.2.2 列表、向量、映射和集

    10.2.3 数学运算、相等和其他操作

    10.3 使用函数和循环

    10.3.1 一些简单的Clojure函数

    10.3.2 Clojure中的循环

    10.3.3 读取器宏和派发器

    10.3.4 函数式编程和闭包

    10.4 Clojure序列

    10.4.1 懒序列

    10.4.2 序列和变参函数

    10.5 Clojure与Java的互操作

    10.5.1 从Clojure中调用Java

    10.5.2 Clojure值的Java类型

    10.5.3 使用Clojure代理

    10.5.4 用REPL做探索式编程

    10.5.5 在Java中使用Clojure

    10.6 Clojure并发

    10.6.1 未来式与并行调用

    10.6.2 ref形式

    10.6.3 代理

    10.7 小结

    第四部分 多语种项目开发

    第11章 测试驱动开发

    11.1 TDD概览

    11.1.1 一个测试用例

    11.1.2 多个测试用例

    11.1.3 深入思考红—绿—重构循环

    11.1.4 JUnit

    11.2 测试替身

    11.2.1 虚设对象

    更多电子书下载就在www.aiyadu.com11.2.2 存根对象

    11.2.3 伪装替身

    11.2.4 模拟对象

    11.3 ScalaTest

    11.4 小结

    第12章 构建和持续集成

    12.1 与Maven 3相遇

    12.2 Maven 3入门项目

    12.3 用Maven 3构建Java7developer项目

    12.3.1 POM

    12.3.2 运行示例

    12.4 Jenkins:满足CI需求

    12.4.1 基础配置

    12.4.2 设置任务

    12.4.3 执行任务

    12.5 Maven和Jenkins代码指标

    12.5.1 安装Jenkins插件

    12.5.2 用Checkstyle保持代码一致性

    12.5.3 用FindBugs设定质量标杆

    12.6 Leiningen

    12.6.1 Leiningen入门

    12.6.2 Leiningen的架构

    12.6.3 Hello Lein

    12.6.4 用Leiningen做面向REPL的TDD

    12.6.5 用Leiningen打包和部署

    12.7 小结

    第13章 快速Web开发

    13.1 Java Web框架的问题

    13.1.1 Java编译为什么不好

    13.1.2 静态类型为什么不好

    13.2 选择Web框架的标准

    13.3 Grails入门

    13.4 Grails快速启动项目

    13.4.1 创建域对象

    13.4.2 测试驱动开发

    13.4.3 域对象持久化

    13.4.4 创建测试数据

    13.4.5 控制器

    13.4.6 GSPJSP页面

    13.4.7 脚手架和UI的自动化创建

    13.4.8 快速周转的开发

    13.5 深入Grails

    13.5.1 日志

    更多电子书下载就在www.aiyadu.com13.5.2 GORM:对象关系映射

    13.5.3 Grails插件

    13.6 Compojure入门

    13.6.1 Hello Compojure

    13.6.2 Ring和路由

    13.6.3 Hiccup

    13.7 我是不是一只水獭

    13.7.1 项目设置

    13.7.2 核心函数

    13.8 小结

    第14章 保持优秀

    14.1 对Java 8的期待

    14.1.1 lambda表达式(闭包)

    14.1.2 模块化(拼图Jigsaw)

    14.2 多语言编程

    14.2.1 语言的互操作性及元对象协议

    14.2.2 多语言模块化

    14.3 未来的并发趋势

    14.3.1 多核的世界

    14.3.2 运行时管理的并发

    14.4 JVM的新方向

    14.4.1 VM的合并

    14.4.2 协同程序

    14.4.3 元组

    14.5 小结

    附录A java7developer:源码安装

    A.1 java7developer的源码结构

    A.2 下载并安装Maven

    A.3 构建java7developer

    A.3.1 一次性的构建准备工作

    A.3.2 clean

    A.3.3 compile

    A.3.4 test

    A.4 小结

    附录B glob模式语法及示例

    B.1 glob模式语法

    B.2 glob模式示例

    附录C 安装备选JVM语言

    C.1 Groovy

    C.1.1 下载Groovy

    C.1.2 安装Groovy

    C.2 Scala

    C.3 Clojure

    更多电子书下载就在www.aiyadu.comC.4 Grails

    C.4.1 下载Grails

    C.4.2 安装Grails

    附录D Jenkins的下载和安装

    D.1 下载Jenkins

    D.2 安装Jenkins

    D.2.1 运行WAR文件

    D.2.2 安装WAR文件

    D.2.3 安装独立安装包

    D.2.4 Jenkins的首次运行

    附录E java7developer:Maven POM

    E.1 构建配置

    E.2 依赖项管理

    更多电子书下载就在www.aiyadu.com版权声明

    Original English language edition, entitled The Well-Grounded Java Developer: Vital Techniques of

    Java 7 and Polyglot Programming by Benjamin J. Evans, Martijn Verburg, published by Manning

    Publications. 178 South Hill Drive, Westampton, NJ 08060 USA. Copyright ? 2013 by Manning

    Publications.

    Simplified Chinese-language edition copyright ? 2013 by Posts Telecom Press. All rights

    reserved.

    本书中文简体字版由Manning Publications授权人民邮电出版社独家出版。未经出版者书面许

    可,不得以任何方式复制或抄袭本书内容。

    版权所有,侵权必究。

    更多电子书下载就在www.aiyadu.com序

    “Kirk说加油站也卖啤酒。”这是Ben Evans跟我说的第一句话。他来克里特岛参加一个开放型

    Java会议。我说我通常到加油站就是加油,但那边拐角确实有个店卖啤酒。Ben看起来对我

    的回答有点儿失望。我在这个希腊小岛上生活了5年,还从来没在加油站买过啤酒。

    当我在看这本书时,那种似曾相识的感觉又来了。我自认为是一名Java专家:用Java写了15

    年程序,发表了几百篇文章,在各种会议中演讲,还执教Java高级课程。可阅读Ben和

    Martijn的这本大作,经常能给我一些意料之外的启发。他们一开始先介绍了为改变Java生态

    系统所做的开发工作。类库的内部实现修改起来相对容易,一般也能见到成效。例

    如,Arrays.sort的内部实现在Java 7中不再用MergeSort算法,而是改用了TimSort。由于

    这个变化,你不用修改自己对偏序数组进行排序的代码就可能看到性能的提升。然而,修改

    类文件格式或添加新的VM特性则需要大量工作。Ben了解这些情况,因为他在JCP执行委员

    会任职。这本书也是关于Java 7的,所以你能接触到Java 7中的所有新特性,比如语法糖的完

    善、String上的switch、分支合并,还有Java NIO.2。

    并发就是线程和同步,对吗?如果这就是你对多线程的认识,那么你需要学习新知识了。就

    像作者在书中指出的,“并发领域的研究工作正开展得热火朝天”。与并发相关的邮件列表上

    每天都有讨论,新点子层出不穷。本书会告诉你如何看待分而治之策略以及如何规避某些安

    全陷阱。

    在我看到类加载那一章时,我觉得他们说得有点儿过了。那都是我和朋友们过去用来炫耀的

    技巧,居然也给摆出来供大家研习了!他们讲解了javap(这个小工具用于透视Java编译器

    生成的字节码)的工作方式,还谈到了新的invokedynamic指令,并解释了它跟普通反射的

    区别。

    我特别喜欢讲性能调优的第6章。除了Jack Shirazi的Java Performance Tuning,这还是第一本

    能够抓住“如何使系统运行更快”这个本质问题的书。我可以用四个字来总结这一章的内

    容:“测量,别猜。”这是做好性能调优的本质,因为人们不可能猜到运行慢的是哪段代码。

    这一章从硬件的角度解读了性能方面的问题,而不是只提供编码技巧。作者还向你展示了如

    何测量性能。有一个挺有意思的基础测试小工具——CacheTester类,可用于查看缓存未命

    中时的开销。

    本书第三部分介绍了JVM上的多语言编程。Java不仅仅是Java编程语言,它还是一个可以运

    行其他语言的平台。我们已经见过不同类型语言的爆炸式增长了。有些是函数式的,有些是

    声明式的,还有一些是平台的接口(Jython和JRuby),让其他语言可以在JVM上运行。语言

    分为动态的(如Groovy)和静态的(如Java和Scala)。在JVM上我们可能因为多种原因而使

    用非Java的语言。如果正好要开始一个新项目,在做决定之前先看看都有什么可用吧。你可

    能不用再写那么多套路化的代码了。

    Ben和Martijn向我们介绍了三种备选语言:Groovy、Scala和Clojure。在我看来,它们是当下

    最切实可行的选择。作者描述了这些语言之间的差异、与Java的差异以及它们的特性。不需

    要太多的技术细节,介绍每种语言的各章足以帮你弄清楚应该用哪一种。别指望能在书中看

    更多电子书下载就在www.aiyadu.com到Groovy的参考手册,但你会了解哪种语言更适合你。

    之后,你将深入了解如何进行测试驱动开发以及如何持续集成系统。我发现一件很有意思的

    事,忠实的“老管家”Hudson这么快就被Jenkins取代了。无论如何,这些工具跟Checkstyle和

    FindBugs一样都是项目管理的基本工具。

    你有望通过研读本书成为一名优秀的Java开发人员。不仅如此,你还能了解如何保持优秀。

    Java一直在变。下一版中我们将见到lambda表达式和模块化。1人们也在不断设计新语言,不

    断更新并发结构。你现在了解的很多真相将来可能不再是真相。因此,我们必须活到老学到

    老!

    1 实现Java模块化的Jigsaw项目被延后到Java 9了,至少要到2015年。——译者注

    一天,我又开车路过Ben想买啤酒的那个加油站。在经济状况如此低迷的希腊,它也像很多

    公司一样关张了。我再也不可能知道他们卖不卖啤酒了。

    Heinz Kabutz博士

    知名Java技术教育家、The Java Specialists' Newsletter创始人

    更多电子书下载就在www.aiyadu.com前言

    本书最开始是给德意志银行外汇IT部的新人准备的培训笔记。Ben觉得市面上没有面向经验

    匮乏的Java开发人员的书,所以决定写一本来填补这个空白。

    在德意志银行IT管理团队的支持下,Ben去了比利时的Devoxx会议寻找灵感。在那里他见到

    了IBM的三位工程师(Rob Nicholson、Zoe Slattery和Holly Cummins),他们把他引荐给了伦

    敦Java社区(LJC,伦敦Java用户组)。

    接下来的周六正好是LJC组织的年度开放会议,就在那次会议上,Ben遇到了LJC的一位领导

    者——Martijn Verburg。两人一见如故,把酒言欢,惺惺相惜,大有相见恨晚之意。也正是

    两人对技术和教学的共同热爱促成了本书。

    软件开发是一项社会活动,我们希望能借助本书唱响这一主题。我们认为,虽然在这项活动

    中技术占有很重要的地位,但人与人之间微妙的沟通和交互关系也不容忽视。要在书里轻松

    解释这些东西并不容易,但这一主题自始至终贯穿本书。

    凭借着对技术的执着和对学习的热爱,开发人员孜孜不倦地工作着。我们希望本书讨论的一

    些话题能够激发他们的学习热情。这是一次观光之旅,而不是百科全书式的灌输,这就是我

    们的初衷:帮助你入门,然后让你自己去探索那些激发你想象力的东西。

    本书不仅为大学毕业生准备了接引指南,更为所有心有困惑的Java开发人员提供了指导。因

    为他们都很想知道:“接下来我该学什么?未来要向什么方向发展?我要再好好考虑考虑!”

    从Java 7的新特性到现代软件开发的最佳实践,再到平台的未来发展,本书一路向前,向你

    展示在成长为资深Java开发人员的过程中我们认为至关重要的那些知识。并发、性能、字节

    码和类加载是最让我们着迷的核心技术。我们还会谈到JVM上那些新的非Java语言(即多语

    言编程),因为在接下来的几年里,对于很多开发人员来说它们将变得越来越重要。

    归根结底,这是一次以你和你的兴趣为核心的、具有前瞻性的旅程。我们认为成为一名优秀

    的Java开发人员有助于你彻底投入到工作中去并顺利驾驭开发,也有助于你对不断变化的

    Java世界及它的周边生态系统有更多了解。

    我们希望这本“经验的结晶”对你来说既实用又有趣,希望它能让你深思,同时还能带给你快

    乐。无论如何,写这本书的体验确实如此!

    更多电子书下载就在www.aiyadu.com致谢

    老话说得好,“众人拾柴火焰高”。对于本书来说,这句话非常贴切。如果没有朋友、亲人、同事、同行,甚至偶尔跟我们对立的那些人,我们就不可能完成本书。我们一直都非常幸

    运,因为那些对我们批评最强烈的人也可以算是我们的朋友。

    帮助过我们的人太多了,很难全部列举出来。本书快付印的时候,我们

    在http:www.java7developer.com上发过一个帖子,其中也列出了一批名单,那些人值得我们

    感谢。

    如果名单里遗漏了谁,都怪我们没有牢记您的大名,请接受我们的歉意!下面这些人对本书

    出版都有贡献,在此一并感谢(排名不分先后)。

    更多电子书下载就在www.aiyadu.com伦敦Java社区

    伦敦Java社区(LJC,www.meetup.comlondonjavacommunity)是我们两位作者相遇相知的地

    方。我们要感谢下面这些帮忙审校本书的人:Peter Budo、Nick Harkin、Jodev Devassy、Craig Silk、N. Vanderwildt、Adam J. Markham、“Rozallin”、Daniel Lemon、Frank Appiah、P.

    Franc “Sebkom” Praveen、Dinuk Weerasinghe、Tim Murray Brown、Luis Murbina、Richard

    Doherty、Rashul Hussain、John Stevenson、Gemma Silvers、Kevin Wright、Amanda Waite、Joel

    Gluth、Richard Paul、Colin Vipurs、Antony Stubbs、Michael Joyce、Mark Hindess、Nuno、Jon

    Poulton、Adrian Smith、Ioannis Mavroukakis、Chris Reed、Martin Skurla、Sandro Mancuso和

    Arul Dhesiaseelan。

    在Java语言之外,我们得到了James Cook、Alex Anderson、Leonard Axelsson、Colin Howe、Bruce Durling和Russel Winder博士非常认真的指导。在这里要特别感谢他们。

    我们还要特别感谢LJC JCP委员会成员:Mike Barker、Trisha Gee、Jim Gough、Richard

    Warburton、Simon Maple、Somay Nakhal和David Illsley。

    最后,感谢LJC的发起人Barry Cranford。四年前,他带领几个勇敢的人,怀抱一个梦想创建

    了LJC。现在,LJC已经有约2500名成员,并且很多其他技术社区也发源于它。LJC已经成为

    伦敦技术界的中流砥柱。

    更多电子书下载就在www.aiyadu.comwww.coderanch.com

    我们要感谢Maneesh Godbole、Ulf Ditmer、David O'Meara、Devaka Cooray、Greg Charles、Deepak Balu、Fred Rosenberger、Jesper De Jong、Wouter Oet、David O'Meara、Mark Spritzler

    和Roel De Nijs,因为他们提供了详细的评论和宝贵的反馈。

    更多电子书下载就在www.aiyadu.comManning出版社

    感谢Manning的Marjan Bace接受我们这两个有着疯狂想法的作者。在制作本书出版的整个过

    程中,我们跟许多人打过交道,非常感谢他们:Renae Gregoire、Karen G. Miller、Andy

    Carroll、Elizabeth Martin、Mary Piergies、Dennis Dalinnik和Janet Vail。毫无疑问,还要感谢

    那些我们从未见过面的幕后工作者。没有他们,就没有这本书!

    感谢Candace Gillhoolley在营销上的努力,还有Christina Rudloff和Maureen Spencer一直以来的

    支持。

    感谢John Ryan III在本书付印前对书稿的全面技术审校。

    感谢那些在本书编写的不同阶段阅读原稿并给编辑和我们提供宝贵反馈的审校者:Aziz

    Rahman、Bert Bates、Chad Davis、Cheryl Jerozal、Christopher Haupt、David Strong、Deepak

    Vohra、Federico Tomassetti、Franco Lombardo、Jeff Schmidt、Jeremy Anderson、John Griffin、Maciej Kreft、Patrick Steger、Paul Benedict、Rick Wagner、Robert Wenner、Rodney Bollinger、Santosh Shanbhag、Antti Koivisto和Stephen Harrison。

    更多电子书下载就在www.aiyadu.com特别致谢

    感谢Andy Burgess为本书开发的网站www.java7developer.com,非常棒。感谢实习生Dragos

    Dogaru测试了所有代码示例。

    感谢Matt Raible非常友善地允许我们在第13章引用他关于如何选择Web框架的一些内容。

    感谢Alan Bateman,他领导了Java 7的NIO.2。他的反馈对于向所有Java开发人员普及这个伟

    大“新API”至关重要。

    Jeanne Boyarsky友好地充当了我们最优秀的技术把关者。她果然名不虚传,什么都躲不过她

    鹰一般犀利的眼睛。感谢Jeanne!

    感谢Martin Ling非常细致地讲解了计时硬件,那是第4章介绍相关内容的主要原因。

    感谢Jason Van Zyl,他非常友善地允许我们在第12章引用Sonatype Company出版的Maven: The

    Complete Reference中的一些内容。

    感谢Kirk Pepperdine对第6章的反馈和意见,还有他的热情及对这个行业独到的见解。

    感谢Heinz M. Kabutz博士为我们写的推荐序和他在克里特岛的盛情款待,还有非常赞的Java

    专家简报(www.javaspecialists.eu)。

    更多电子书下载就在www.aiyadu.comBen Evans的致谢

    许多人都以不同的方式为本书作出了贡献,但篇幅有限,就不一一致谢了。在此,特别感谢

    下面这些人。

    感谢Bert Bates和其他Manning的员工,他们让我了解了稿件和书之间的区别。

    感谢Martijn,当然,为了友谊,为了能帮我度过难关坚持写作,为了很多东西,总之一言难

    尽。

    感谢我的家人,特别是我的外祖父John Hinton和祖父John Evans,我之所以成为我,是因为从

    他们那里继承了很多东西。

    最后,感谢E-J(水獭在书中出现如此频繁的原因)和Liz,太多夜晚因为写作不能陪他们,感谢他们的理解。我的爱,献给他们。

    更多电子书下载就在www.aiyadu.comMartijn Verburg的致谢

    感谢我的妈妈Janneke和爸爸Nico,感谢你们在我和姐姐小时候把Commodore 64电脑带回了

    家。尽管玩《跳跳人》(这真的是一个非常非常酷的平台游戏。老妈拿着操纵杆跑来跑去真

    是太好笑了:-))几乎成了我们在家里用电脑做得最多的事,但正是它的编程手册激发了我

    对技术的热情。爸爸还教会了我一个道理:如果你能把小事情做对,那么由小事情组成的大

    事也就不在话下了。我至今依然在编码和日常工作中将它奉为真理。

    感谢我的姐姐Kim,感谢你小时候跟我一起写代码!我永远也忘不了第一个星域(缓慢地

    ——那时,我可不太擅长做性能调优)出现在屏幕上时的场景,魔法真的显灵了!姐夫Jos

    给了我们俩很多灵感(不仅仅因为他是一位空间科学家,尽管那确实很酷!)。我那超级可

    爱的外甥女Gweneth也出现在了本书里,看你能不能找到她!

    Ben是我认识的业内最棒的技术专家之一。有时他的技术水平可以用“吓人”来形容!我很荣

    幸能跟他合写本书,没想到关于JVM还有那么多知识我不知道。Ben一直都是LJC的优秀领导

    者,我俩在技术大会上一唱一和的演讲之后,甚至有人认为我们该一块去演喜剧了。能跟朋

    友合写一本书,真好。

    最后,感谢我可爱的妻子Kerry,为了保证本书每一章的插图和屏幕截图都恰到好处,我们

    一次次地取消了活动计划,而你毫无怨言——你总是那么迷人。愿每个人都能拥有这样的爱

    和支持。

    更多电子书下载就在www.aiyadu.com关于本书

    欢迎阅读本书。通过阅读本书,你将成为紧跟时代潮流的Java程序员,重燃对这一语言和平

    台的热情。学习过程中,你会发现Java 7的新特性,熟悉重要的现代软件技术(比如依赖注

    入、测试驱动开发和持续集成),并开始探索JVM上的非Java语言这个美丽新世界。

    首先,我们来看看James Iry在他精彩的博文“A Brief, Incomplete,and Mostly Wrong History of

    Programming Languages”(简明、不完整并且漏洞百出的编程语言历史)中对Java语言的描

    述:

    1996年,James Gosling发明了Java。Java相对繁琐、基于类,是支持垃圾收集、静态类

    型、单派发的面向对象语言,继承方式为实现单继承和接口多继承。Sun大肆宣扬Java的

    新颖性。

    他对Java的描述基本上是在插科打诨,C在文中也受到了同等待遇。但作为对一种语言的描

    述,这种方式也不赖。博文还有很多精彩之处,参见James的博客(http:james-

    iry.blogspot.com)。没事的时候看看还是挺有收获的。

    James的描述的确提出了一个很实际的问题。为什么我们还要讨论一种有将近16年历史的语

    言呢?它真的已经稳定,没有多少新东西或有意思的事情值得探讨了吗?

    如果真是那样,本书就会很薄。事实是,我们依然在谈论Java,因为它的一大优点就是其在

    以下几个核心设计决策之上的构造能力,这些都已经在市场中获得了成功:

    运行时环境的自动管理(比如垃圾收集、即时编译);

    语法简单,核心概念相对较少;

    保守的语言进化方式;

    在类库中增加功能和复杂性;

    广泛、开放的生态系统。

    这些设计决策一直在推动着Java世界的创新,简单的核心使得开发门槛很低,而广阔的生态

    系统使得后来者很容易找到适合自己需要的现成组件。

    尽管从历史趋势上来看语言的变化很缓慢,但这些特质使得Java平台和语言既强大又充满活

    力。Java 7仍然延续了这一趋势。语言的改变是演进式,而不是革命式的。然而,Java 7跟之

    前版本相比有一个主要区别:它是第一个明确着眼于下一次发布的新版本。根据Oracle有关

    发布的“B计划”,Java 7为Java 8的主要变化打下了基础。

    近年来,JVM上非Java语言的崛起也是一个重大变化。这引发了Java和其他JVM语言之间的

    相互融合。现在有大量的项目完全运行在JVM之上(这个数量还在增加),而Java只是它们

    所用的编程语言之一。

    多语言特别是涉及Groovy、Scala和Clojure语言的项目,是当前Java生态系统的一个重要因

    更多电子书下载就在www.aiyadu.com素,也是本书最后一部分的主题。

    更多电子书下载就在www.aiyadu.com阅读须知

    本书内容大体上适合顺序阅读,但我们也能理解某些读者想直奔主题的心情,因此也在一定

    程度上迎合了这种阅读需求。

    我们非常认同自己动手的学习方法,所以建议读者在阅读的同时尝试示例代码。接下来介绍

    本书主要内容,希望习惯跳跃阅读的读者能从这里找到线索。

    本书分四部分:

    用Java 7做开发;

    关键技术;

    JVM上的多语言编程;

    多语种项目开发。

    第一部分共两章,都是关于Java 7的内容。本书通篇使用Java 7的语法和语义,所以第1章“初

    识Java 7”是必读的。那些要处理文件、文件系统和网络IO的开发人员应该会对第2章“新

    IO”特别感兴趣。

    第二部分共四章(第3~6章),涉及的主题包括依赖注入、现代并发、类文件字节码以及性

    能调优。

    第三部分共四章(第7~10章)介绍了JVM上的多语言编程。第7章是必读的,因为这一章介

    绍的JVM上可用语言的类型和使用是阅读后面章节的基础。接下来的三章分别介绍与Java类

    似的语言Groovy、兼具OO和函数式特色的混合语言Scala和纯函数式语言Clojure。刚接触函

    数式编程的开发人员可能需要按顺序阅读,但这几章本身是相互独立的,可以跳着读。

    第四部分(最后四章)在之前内容基础上介绍了新内容。虽然各章可以独立阅读,但是在某

    些部分我们会假定你已经读过之前的内容,或者已经熟悉那些主题。

    简言之,如果整本书你必看一章,那就看第1章。如果你会看第三部分,那一定要看第7

    章。其他各章既可以顺序阅读,也可以独立阅读,但后面的某些章节会假定你已经看过前面

    的内容。

    更多电子书下载就在www.aiyadu.com读者对象

    本书主要是为那些希望掌握Java语言和平台现代化知识的开发人员写的。如果你想跟上Java

    7的步伐,就请阅读本书吧。

    如果你想提升一下自己的技能,想搞清楚依赖注入、并发、测试驱动开发之类的主题,本书

    能为你打下良好的基础。

    本书也是为已经认识到多语言编程趋势并想深入下去的开发人员准备的。具体来说,如果你

    想学习函数式编程,那么本书介绍Scala和Clojure的两章会很有帮助。

    更多电子书下载就在www.aiyadu.com路线图

    第一部分只有两章。第1章介绍了Java 7及其Coin项目,该项目包含很多小巧高效的特性。第

    2章全面介绍了新IO API,包括对文件系统API的全面梳理,还介绍了新的异步IO能力。

    第二部分分四章介绍了Java 7的关键技术。第3章告诉你依赖注入技术的源流,接着展示了

    Java中的标准解决方案Guice 3。第4章阐述在Java中如何正确进行现代并发开发。因为硬件

    行业坚定地朝着多核处理器方向发展,这个话题再次成为焦点。第5章介绍了JVM的类文件

    和字节码,揭示了它们的秘密,让你明白Java的工作原理。第6章讲解Java应用程序调优的基

    础知识,并讨论垃圾收集器等内容。

    第三部分介绍JVM上的多语言编程,由四章组成。多语言编程的内容从第7章开始,这里讲

    述了多语言编程背景知识,以及使用另一种语言的恰当时机。第8章介绍了Groovy——Java

    动态编程的朋友。Groovy突显了语法相似的动态语言如何大幅提升Java开发人员的生产率。

    第9章将你带入函数式OO混合的Scala世界。Scala是一种强大精炼的语言。第10章是为Lisp

    粉丝们准备的。Clojure被广泛誉为“使用得当的Lisp”,它全面展示了JVM上函数式语言的力

    量。

    第四部分以前三部分的内容为基础,讨论多语言编程技术在几个编程领域涉及的问题。第11

    章谈到了测试驱动开发,还提供了一个围绕处理模拟对象的方法,给出了一些实战建议。第

    12章介绍了两种得到广泛应用的工具,用于构建流程中的Maven 3和用于持续集成的

    JenkinsHudson。第13章涵盖了与快速Web开发相关的主题,解释了Java在这一领域的传统缺

    陷,并提供了一些原型化的新技术(Grails和Compojure)。第14章是对全书的总结和对未来

    的展望,其中包括Java 8可能支持的新功能。

    更多电子书下载就在www.aiyadu.com代码约定及下载

    首先需要下载和安装的是Java 7。只要找到适合你的OS的发布包,按照下载和安装说明来做

    就行了。Oracle网站上Java SE部分有安装包的在线下载和说明,网址

    是www.oracle.comtechnetworkjavajavasedownloadsindex.html。

    另请参见附录A中的安装说明以及源码运行的指南。

    书中所有源码都是等宽字体,以区别于周围的文字。很多代码清单中都有注解,指出其中的

    关键概念,有时候文中使用带圆圈的数字,对代码进行更详细的注解。我们尽量按照版心的

    宽度设置代码格式,也在必要时换行,并谨慎使用缩进。但有时候代码行太长,因此会有续

    行标记。

    书中所有示例的源码都可在www.manning.comTheWell-GroundedJavaDeveloper找到。1书中自

    始至终都有示例代码。比较长的代码清单有清楚的清单标题,短一些的代码清单就在文字的

    行与行之间。

    1 本书源码也可在图灵社区http:www.ituring.com.cnbook1027下载。——编者注

    更多电子书下载就在www.aiyadu.com软件需求

    如今,Java 7几乎可运行在任何现代平台上。只要你使用以下某个操作系统,就可以运行源

    码示例:

    MS Windows XP及以上版本;

    较新版的nix;

    Mac OS X 10.6及以上版本。

    多数人都想在IDE中试验代码示例。以下这些主流IDE都对Java 7和最新版的Groovy、Scala、Clojure提供良好支持:

    Eclipse 3.7.1及以上版本;

    NetBeans 7.0.1及以上版本;

    IntelliJ 10.5.2 及以上版本。

    我们使用了NetBeans 7.1和Eclipse 3.7.1来创建和运行代码示例。

    更多电子书下载就在www.aiyadu.com作者在线

    购买本书英文版的读者可以免费访问由Manning出版社运营的专用Web论坛,并在论坛中对该

    书进行评论、提出技术问题、从作者和其他用户那里得到帮助。要访问并订阅该论坛,请访

    问www.manning.comTheWell-GroundedJavaDeveloper,这个页面介绍了注册后如何访问论

    坛、可以得到什么帮助以及论坛上的行为规则。

    Manning向读者承诺为读者之间、读者与作者之间提供一个可以对话的场所,但不会强制作

    者参与,作者在论坛上的贡献都是自愿而且不收费的。为使作者感兴趣,提高其参与度,我

    们建议读者向作者提一些具有挑战性的问题。

    只要本书英文版仍然在售,读者就可以从出版社的网站上访问作者在线论坛和之前讨论话题

    的归档。

    更多电子书下载就在www.aiyadu.com关于作者

    Ben Evans是伦敦Java用户组的发起人、协助定义Java生态系统标准的Java社区过程执行委员

    会成员。他在技术圈已经度过了很多年“有趣的时光”,现为一家面向金融业的Java技术公司

    的CEO。Ben经常在公开场合发表关于Java平台、性能和并发的演讲。

    Martijn Verburg(是jClarity的CTO)作为一名技术专家和众多初创企业的OSS导师,拥有十多

    年的经验。他也是伦敦Java用户组的领导者,带领全球的Java用户组成员为JSR(采用JSR计

    划)和OpenJDK(采用OpenJDK计划)作出了贡献。作为一位公认的技术团队优化专家,他

    经常应邀出席Java办的大型会议(JavaOne、Devoxx、OSCON、FOSDEM等)并发表演讲,人送雅号“开发魔头”,赞颂他敢于向行业现状挑战的精神。

    更多电子书下载就在www.aiyadu.com关于封面图片

    本书封面上的画像标题为“卖花人”,摘自19世纪法国出版的沙利文?马雷夏尔(Sylvain

    Maréchal)四卷本的地域服饰风俗纲要。其中每幅插图都是手工精心绘制并上色的。马雷夏

    尔这套书展示的丰富服饰,令我们强烈感受到200年前乡村与城镇的巨大文化差异。不同地

    域的人山水阻隔,言语不通。无论奔走于街巷,还是驻足于乡间,通过他们的服饰,一眼就

    能看出他们的生活场所、职业,以及生活境况。

    时过境迁,书中描绘的那些区域性服饰差异到如今已经不复存在。即使是不同国家,都很难

    再看出人们着装的区别,再不必说城镇和乡村了。或许,我们今天多姿多彩的人生,正是从

    前那些文化差异的体现。只不过,如今的生活更加多元,而且技术环境下的生活节奏也更快

    了。

    今时今日,计算机图书层出不穷,Manning就以马雷夏尔这套书中多样性的图片,来表达对

    IT行业日新月异的发明与创造的赞美。

    更多电子书下载就在www.aiyadu.com第一部分 用Java 7做开发

    本书前两章主要讨论Java 7的高明之处。为便于读者理解下文,第1章先介绍了一些可提高开

    发人员工作效率的语法变化,这些变化并不大,但效果都比较显著。第1章在这一部分中主

    要起抛砖引玉的作用,而另一个主题,Java中的新IO才是主角。

    优秀的Java开发人员要了解语言的新特性。Java 7中的新特性可以使开发人员的工作变得更

    轻松。但对于这些新变化,光了解语法是不够的。为了能迅速写出高效、安全的代码,你

    还需要对实现这些新特性的原因和方式有深刻的认识。Java 7的变化可以大致分为两块:

    Coin项目和NIO.2。

    第一块是Coin项目,包括语言层面的一些小变化,设计它们的初衷是提高开发人员的生产

    率,但又不会对底层平台造成太大影响。这些变化包括:

    try-with-resources结构(可以自动关闭资源);

    switch中的字符串;

    对数字常量的改进;

    Multi-catch(在一个catch块中声明多个要捕获的异常);

    钻石语法(在处理泛型时不用那么繁琐了)。

    这些变化看起来都不大,但探索这些简单的语法修改背后的语义迁移,能让你洞察Java语言

    和Java平台之间的差别。

    第二块变化是新IO(NIO.2)API,跟Java原有的文件系统支持相比,它具有压倒性优势,还

    提供了强大的异步能力。这些变化包括:

    用于引用文件和类文件实体的新Path结构;

    简化文件的创建、复制、移动和删除的工具类Files;

    内建的目录树导航;

    在后台处理大型IO的将来式和回调式异步IO。

    第一部分结束时,你会很自然地用Java 7的方式来思考问题和编写代码。我们在后续章节中

    还会用到Java 7中的新特性,所以你还有机会不断温习这些新知识。

    更多电子书下载就在www.aiyadu.com第1章 初识Java 7

    内容提要

    Java既是编程语言,也是平台

    语法变一点,能力强好多

    try-with-resources语句

    提升异常处理能力

    欢迎进入Java 7的世界!斗转星移,时过境迁。当尘埃落定,我们终于见到了Java 7的真容。

    虽然看起来有点陌生,但它必将带来全新的体验!跟随我们经历过这段探索之旅,你将进入

    更广阔的世界,发现更多新特性、更高明的编程技巧,并接触到JVM上运行的更多编程语

    言。

    现在,我们先来热热身。虽然只是简单介绍,但还是能让你了解Java 7的强大特性。我们会

    先解释Java语言和平台的区别,因为有时人们会对这两种说法产生误解。

    接着我们会介绍Coin项目,它汇聚了Java 7里短小精悍的新特性。我们会向你展示Java平台

    所接受、吸纳和发布的那些特性,就是它们构成了Java的变化。在此之后,我们会介绍Coin

    项目中新引入的6个主要特性。

    你会学到新的语法,比如改进的异常处理方式(multi-catch)以及 try-with-resources结构,借

    此在处理文件或其他资源的代码中躲开那些烦人的bug。读完本章,你将能用全新的方式编

    写Java代码,并整装待发,准备好迎接更大的挑战!

    让我们先来探讨一下Java作为语言和平台的双重角色,这是现代Java的核心。这个知识点将

    贯穿全书,是一个必须掌握的基本要点。

    更多电子书下载就在www.aiyadu.com1.1 语言与平台

    使用Java之前,我们要先弄清楚Java语言和Java平台之间的区别。然而,有时候不同的作者

    对语言和平台的构成会有不同的定义,所以人们有时不太清楚两者之间的区别,分不清是语

    言还是平台提供了代码使用的编程特性。

    因为本书的大部分内容都需要你理解两者的区别,所以这里需要说明一下。以下是我们给出

    的定义。

    Java语言 在“关于本书”中,我们提到Java语言是静态类型、面向对象的语言,希望你

    对这种说法已经非常熟悉了。Java语言还有一个非常明显的特点,它是(或者说应该

    是)人类可读的。

    Java平台 平台是提供运行时环境的软件。Java虚拟机(JVM)负责把类文件形式(人

    类不可读)的代码链接起来并执行。JVM不能直接解释Java语言的源文件,你要先把源

    文件转换成类文件。

    Java作为软件系统之所以能成功,主要因为它是一种标准。也就是说,它有规范文件描述它

    应该如何工作。不同的厂商或项目组可以据此推出自己的实现,这些不同实现的工作方式在

    理论上是相同的。规范虽然不能保证这些实现处理同一任务时表现如何,但可以保证处理结

    果的正确性。

    控制Java系统的规范有多种,其中最重要的是《Java语言规范》(JLS)和《JVM规范》

    (VMSpec)。在Java 7中,这两者之间的界限愈发清晰。实际上,VMSpec不再引用JLS中的

    任何内容,如果你认为这是Java 7重视Java之外其他语言的信号,说明你有见微知著的能

    力!希望你能继续关注,接下来我们会更加深入地探讨这两个规范之间的差别。

    提到Java的双重角色,你自然想问:“它们两者之间还有什么关联吗?”如果它们在Java 7中

    如此泾渭分明,又是如何共同形成我们所熟悉的Java系统的呢?

    连接Java语言和平台之间的纽带是统一的类文件(即.class文件)格式定义。认真研究类文件

    的定义能让你获益匪浅,这是优秀Java程序员向伟大Java程序员转变的一个途径。图1-1展示

    了产生和使用Java代码的整个过程。

    更多电子书下载就在www.aiyadu.com图1-1 Java源码被转换成 .class 文件,在JIT编译前被加载处理

    如图所示,Java代码的演进过程从我们可以看懂的Java源码开始,然后由javac编译成.class

    文件,变成可以加载到JVM中的形式。值得注意的是,类文件在加载过程中通常都会被处理

    和修改。大多数流行框架(特别是打着“企业级”旗号的)都会在类加载过程中对类进行改

    造。

    Java是编译型语言还是解释型语言?

    大多数开发人员都知道,Java源文件需要编译成.class文件才能在JVM中运行。如果继续追

    问,许多开发人员还会告诉你说.class中的字节码首先会被JVM解释,但在稍后即时

    (JIT)编译。然而很多人将字节码含糊地理解为“在某种虚构的或简化的CPU上运行的机

    器码”。

    实际上,JVM字节码更像是中途的驿站,是一种从人类可读的源码向机器码过渡的中间状

    态。用编译原理术语讲,字节码实际上是一种中间语言(IL)形态,不是真正的机器码。

    也就是说,将Java源码变成字节码的过程不是C或C++程序员所理解的那种编译。Java所谓

    的编译器javac也不同于gcc,实际上它只是一个针对java源码生成类文件的工具。Java体系

    中真正的编译器是JIT,如图1-1所示。

    有人说Java是“动态编译”的,他们所说的编译是指JIT的运行时编译,不是指构建时创建类

    文件的过程。

    所以如果被问及“Java是编译型语言还是解释型语言”,你可以回答“都是”。

    希望我们已经把Java语言和Java平台之间的区别解释清楚了。接下来我们进入下一话题,看

    看Java 7中一些语法上的调整,先从Coin项目中的那些小变化开始。

    更多电子书下载就在www.aiyadu.com1.2 Coin项目:浓缩的都是精华

    自2009年1月起,Coin便是Java 7(和Java 8)中一个开源的子项目。本节,我们会以Coin项

    目中包含的小变化为例,解释一下Java语言如何演进以及那些特性是如何被选中的。

    为Coin项目命名

    创建Coin项目是为了反映Java语言中的微小变动。项目的名字是个双关语——像硬币一样

    小的变化(small change comes as coins),而“套用一句老话”(to coin a phrase)指的是给

    Java语言添一个新的表述方式。

    在技术圈子里,这种文字游戏、奇思妙想和躲不掉的恐怖双关语随处可见。你可能已经对

    此习以为常了。

    我们觉得解释语言“为什么要变”和“变成了什么”同样重要。在开发Java 7的过程中,人们对

    新语言特性产生了很多兴趣,但Java社区有时并不明白要按时实现这些特性需要多大工作

    量。希望我们在“为什么要变”这一问题上能够对你有所启示,也希望能借此消除一些荒诞的

    说法。如果你对Java如何发展进化不感兴趣,可以直接阅读1.3节,看看Java语言发生了哪些

    变化。

    关于修改Java语言有一个工作量曲线,某些实现方式可能要比其他方式需要的工作量少。如

    图1-2所示,我们尽量把不同的修改方式及其所需的工作量呈现出来,以展现不同复杂度及

    相关工作量的增长。

    图 1-2 用不同方式提供新功能所需工作量的比较

    一般来说,工作量越少越好。也就是说,如果能用类库实现新特性,那就应该用类库。但有

    些特性用类库或增加IDE功能实现起来有难度,甚至根本不可能,那就必须在平台的更深层

    中实现。

    下面是一些特性(大部分是Java 7中的),它们符合我们为语言新特性定义的复杂度。

    更多电子书下载就在www.aiyadu.com语法糖——数字中的下划线(Java 7);

    新的语言小特性——try-with-resources(Java 7);

    类文件格式的变化——注解(Java 5);

    JVM的新特性——动态调用(Java 7)。

    语法糖

    “语法糖”是描述一种语言特性的短语。它表示这是冗余的语法——在语言中已经存在一种

    表示形式了——但语法糖用起来更便捷。

    一般来说,程序的语法糖在编译处理早期会从编译结果中移除,变为相同特性的基础表示

    形式,这称为“去糖化”。

    因此,语法糖是比较容易实现的修改。它们通常不需要做太多工作,只需要修改编译器

    (对于Java来说就是javac)。

    Coin项目中(以及本章余下的内容)都是关于从Java语法糖到小的新特性这个范围之内的变

    化。

    Coin项目的最初建议阶段从2009年2月持续到3月,coin-dev的邮件列表上收到了大约70个提

    议,囊括了各种可能的改进。甚至有人开玩笑说要增加lolcat风格的多行字符串(把标题叠

    加在好笑或生气的猫咪图片上,看你怎么想了——http:icanhascheezburger.com)。

    Coin项目提案的评判规则很简单。贡献者要完成三项任务:

    提交一份详细的提案来描述修改(本质上应该是对Java语言的修改,而不是针对虚拟机

    的);

    在邮件列表上针对提案进行开放式讨论,能够接受其他参与者建设性的批评和建议;

    随时可以提供一组能够实现变化的补丁原型。

    Coin项目很好地示范了语言和平台在未来如何演进,所有的修改都会公开讨论,提供特性的

    早期原型,并且呼吁公众参与。

    “什么是对规范的小修改?”现在也许就是提出这个问题的最好时机。我们马上要讨论在JLS

    的第14.11节中增加的一个单词——String。你可能再也找不出比这个更小的修改了,可

    即便是这样一个修改都会涉及规范中其他几个地方。

    Java 7是以开源方式开发后发布的第一个版本

    Java一开始并不是开源语言,但在2006年的JavaOne大会上其自身的源码以GPLv2许可发布

    (去掉了一些Sun不具有版权的源码)。当时正值Java 6发布前后, 所以Java 7是Java在开

    源软件(OSS)许可下发布的第一版。开源的Java平台开发主要集中在项目OpenJDK上。

    邮件列表coin-dev、lambda-dev和mlvm-dev等是讨论未来各种可能特性的主要场所,来自

    五湖四海的开发人员都可以借此参与到创造Java 7的过程中来。实际上,我们参与领导

    了“Adopt OpenJDK”计划,为新加入OpenJDK的开发人员提供指导,帮助改进Java。如果

    更多电子书下载就在www.aiyadu.com你想加入我们,请访问http:java.netprojectsjugspagesAdoptOpenJDK。

    任何修改都会产生影响,我们必须从Java语言的整体设计上来追踪这些影响。

    任何修改都应该严格执行下面这些操作(或至少调研一下):

    更新JLS;

    在源码编译器中实现一个原型;

    为修改增加必要的类库支持;

    编写测试和示例;

    更新文档。

    除此之外,如果修改触及VM或者平台,应该:

    更新VMSpec;

    实现VM的修改;

    在类文件和VM工具中增加支持;

    考虑对反射的影响;

    考虑对序列化的影响;

    想一想对本地代码组件的影响,比如Java本地接口(JNI)。

    考虑到这些修改对整体语言规范产生的影响,这可不是一星半点儿的工作。

    如果你要修改类型系统,简直就是自寻死路,类型系统可是个不折不扣的雷区。这不是因为

    Java的类型系统不好,而是因为对于拥有多种静态类型系统的语言来说,这些类型系统之间

    有可能会产生很多交叉点。修改它们很容易出现意想不到的状况。

    Coin项目选的路线非常明智,它建议贡献者们在修改提案中远离类型系统。因为对这种修改

    而言,即使最小的变化都需要做大量的工作,所以这种做法也是比较务实的。

    在简单介绍了Coin项目的背景之后,接下来该看看它包含哪些特性了。

    更多电子书下载就在www.aiyadu.com1.3 Coin项目中的修改

    Coin项目主要给Java 7引入了6个新特性,它们分别是switch语句中的String、数字常量的

    新形式、改进的异常处理、try-with-resources、钻石语法,还有变参警告位置的修改。

    我们会详细讲解Coin项目中的这些变化,讨论这些新特性的语法和含义,并尽可能解释推出

    这些特性背后的动机。当然,我们也不是要把提案全部照搬过来,coin-dev邮件列表的归档

    里有完整的提案,如果你是一个好奇的语言设计师,可以去那里看看,还可以和大家讨论你

    的想法。

    闲言少叙,开始介绍第一个Java 7新特性——switch语句中的String值。

    1.3.1 switch语句中的String

    switch语句是一种高效的多路语句,可以省掉很多繁杂的嵌套if判断,比如像这样:

    public void printDay(int dayOfWeek) {

    switch (dayOfWeek) {

    case 0: System.out.println(Sunday); break;

    case 1: System.out.println(Monday); break;

    case 2: System.out.println(Tuesday); break;

    case 3: System.out.println(Wednesday); break;

    case 4: System.out.println(Thursday); break;

    case 5: System.out.println(Friday); break;

    case 6: System.out.println(Saturday); break;

    default: System.err.println(Error!); break;

    }

    }

    在Java 6及之前,case语句中的常量只能是byte、char、short和int(也可以是对应的封装

    类型 Byte、Character、Short和Integer)或枚举常量。Java 7规范中增加了String,毕

    竟它也是常量类型。

    public void printDay(String dayOfWeek) {

    switch (dayOfWeek) {

    case Sunday: System.out.println(Dimanche); break;

    case Monday: System.out.println(Lundi); break;

    case Tuesday: System.out.println(Mardi); break;

    case Wednesday: System.out.println(Mercredi); break;

    case Thursday: System.out.println(Jeudi); break;

    case Friday: System.out.println(Vendredi); break;

    case Saturday: System.out.println(Samedi); break;

    default: System.out.println(Error: '+ dayOfWeek +' is not a day of the week); break;

    }

    }

    更多电子书下载就在www.aiyadu.com除此之外,switch语句和以前完全一样。像Coin项目中的许多新特性一样,这不过是一个

    让你更轻松的小小改进。

    1.3.2 更强的数值文本表示法

    当时有几个与整型语法相关的提案,最终被选中的是下面这两个:

    数字常量(如基本类型中的integer)可以用二进制文本表示;

    在整型常量中可以使用下划线来提高可读性。

    这两个改变乍看起来都不起眼,但它们确实解决了一直困扰着Java程序员的一些小麻烦。

    这两个新特性对系统底层程序员,就是那些整天处理原始网络协议、加密或沉迷于摆弄比特

    的人们特别有用。先来看一下二进制文本。

    1.二进制文本

    在Java 7之前,如果要处理二进制值,就必须借助棘手(又容易出错)的基础转换,或者调

    用parseX方法。比如说,如果想让int x用位模式表示十进制值102,你可以这样写:

    int x = Integer.parseInt(1100110, 2);

    为了确保x是正确的位模式,你需要敲许多代码。这种方式尽管看起来还行,但实际上存在

    很多问题:

    十分繁琐;

    方法调用对性能有影响;

    需要知道parseInt的双参形式;

    需要记住双参的parseInt的处理细节;

    JIT编译器更难实现;

    用运行时的表达式表示应该在编译时确定的常量,导致x不能用在switch语句中;

    如果在位模式中有拼写错误(能通过编译),会在运行时抛出RuntimeException 。

    现在好了,用Java 7可以写成:

    int x = 0b1100110;

    我们没说这种方法无所不能,但它确实解决了上面提到的那些问题。

    你在跟二进制打交道时,这个小特性会是你的得力助手。比如在处理字节时,可以

    在switch语句中使用由位模式定义的二进制常量。

    另外一个新特性虽然小,但却很实用——可以在表示一组二进制位或其他长数值的数字中加

    入下划线。

    更多电子书下载就在www.aiyadu.com2.数字中的下划线

    众所周知,人脑和电脑有很多不同的地方,对于数字的处理方式就是其中之一。通常人们都

    不太喜欢面对一大串数字。这也是我们发明十进制的原因之一——因为人脑更擅于处理信息

    量大的短字串,而不是每个字符信息量都不太多的长字串。

    也就是说,我们觉得1c372ba3要比00011100001101110010101110100011更容易处理,但

    电脑只认第二种。人们在处理长串数字时会采用分隔法,比如用404-555-0122表示电话号

    码。

    注意 如果你跟作者(欧洲人)一样,想知道为什么美国电影或书里的电话号码总是以555

    开头,我可以告诉你。555-01xx是保留号段,用于虚构的情境。这是为了避免现实生活中

    的人接到那些对好莱坞电影过分投入的人打来的电话。

    其他带有分隔符的一长串数字:

    100 000 000美元(一大笔钱);

    08-92-96(英国银行的排序代码)。

    可在代码中处理数字时不能用逗号(,)和连字符(-)作分隔符,因为它们可能会引发歧

    义。Coin项目中的提案借用了Ruby的创意,用下划线(_)作分隔符。注意,这只是为了让

    你阅读数字时更容易理解而做的一个小修改,编译器会在编译时把这些下划线去掉,只保留

    原始数字。

    也就是说,为了不把100 000 000 和10 000 000搞混,你可以在代码中将100 000 000写成

    100_000_000,以便很容易区分它和10_000_000的差别。来看下面两个例子,至少你应该对

    其中一个比较熟悉:

    long anotherLong = 2_147_483_648L;

    int bitPattern = 0b0001_1100__0011_0111__0010_1011__1010_0011;

    注意:赋给anotherLong的数值现在看起来清楚多了。

    警告 在Java中可以用小写字母l表示长整型数值,比如1010100l。但最好还是用大写字母

    L,以免维护代码的人把数字1和字母l搞混:1010100L看起来要清楚得多。

    现在你应该清楚这些变化给整数处理带来的好处了!让我们继续前进,去看看Java 7中的异

    常处理。

    1.3.3 改善后的异常处理

    异常处理有两处改进——multicatch和final重抛。要知道它们对我们有什么帮助,请先看一段

    Java 6代码。下面这段代码试图查找、打开、分析配置文件并处理此过程中可能出现的各种

    异常:

    更多电子书下载就在www.aiyadu.com代码清单1-1 在Java 6中处理不同的异常

    public Configuration getConfig(String fileName) {

    Configuration cfg = null;

    try {

    String fileText = getFile(fileName);

    cfg = verifyConfig(parseConfig(fileText));

    } catch (FileNotFoundException fnfx) {

    System.err.println(Config file ' + fileName + ' is missing);

    } catch (IOException e) {

    System.err.println(Error while processing file ' + fileName + ');

    } catch (ConfigurationException e) {

    System.err.println(Config file ' + fileName + ' is not consistent);

    } catch (ParseException e) {

    System.err.println(Config file ' + fileName + ' is malformed);

    }

    return cfg;

    }

    这个方法会遇到的下面几种异常:

    配置文件不存在;

    配置文件在正要读取时消失了;

    配置文件中有语法错误;

    配置文件中可能包含无效信息。

    这些异常可以分为两大类。一类是文件以某种方式丢失或损坏,另一类是虽然文件理论上存

    在并且是正确的,却无法正常读取(可能是因为网络或硬件故障)。

    如果能把这些异常情况简化为这两类,并且把所有“文件以某种方式丢失或损坏”的异常放在

    一个catch语句中处理会更好。在Java 7中就可以做到:

    代码清单1-2 在Java 7中处理不同的异常

    public Configuration getConfig(String fileName) {

    Configuration cfg = null;

    try {

    String fileText = getFile(fileName);

    cfg = verifyConfig(parseConfig(fileText));

    } catch (FileNotFoundException|ParseException|ConfigurationException e) {

    System.err.println(Config file ' + fileName +

    ' is missing or malformed);

    } catch (IOException iox) {

    System.err.println(Error while processing file ' + fileName + ');

    }

    return cfg;

    }

    异常e的确切类型在编译时还无法得知。这意味着在catch块中只能把它当做可能异常的共

    更多电子书下载就在www.aiyadu.com同父类(在实际编码时经常用Exception或Throwable)来处理。

    另外一个新语法可以为重新抛出异常提供帮助。开发人员经常要在重新抛出异常之前对它进

    行处理。在前几个版本的Java中,经常可以看到下面这种代码:

    try {

    doSomethingWhichMightThrowIOException;

    doSomethingElseWhichMightThrowSQLException;

    } catch (Exception e) {...

    throw e;

    }

    这会强迫你把新抛出的异常声明为Exception类型——异常的真实类型却被覆盖了。

    不管怎样,很容易看出来异常只能是IOException或SQLException。既然你能看出来,编

    译器当然也能。下面的代码中用了Java 7的语法,只改了一个单词:

    try {

    doSomethingWhichMightThrowIOException;

    doSomethingElseWhichMightThrowSQLException;

    } catch (final Exception e) {...

    throw e;

    }

    关键字final表明实际抛出的异常就是运行时遇到的异常——在上面的代码中就

    是IOException或SQLException。这被称为final重抛,这样就不会抛出笼统的异常类

    型,从而避免在上层只能用笼统的catch捕获。

    上例中的关键字final不是必需的,但实际上,在向catch和重抛语义调整的过渡阶段,留

    着它可以给你提个醒。

    Java 7对异常处理的改进不仅限于这些通用问题,对于特定的资源管理也有所提升,我们马

    上就会讲到。

    1.3.4 Try-with-resources(TWR)

    这个修改说起来容易,但其实暗藏玄机,最终证明做起来比最初预想的要难。其基本设想是

    把资源(比如文件或类似的东西)的作用域限定在代码块内,当程序离开这个代码块时,资

    源会被自动关闭。

    这是一项非常重要的改进,因为没人能在手动关闭资源时做到100%正确,甚至不久前Sun提

    供的操作指南都是错的。在向Coin项目提交这一提案时,提交者宣称JDK中有三分之二的

    close用法都有bug,真是不可思议!

    更多电子书下载就在www.aiyadu.com好在编译器可以生成这种学究化、公式化且手工编写易犯错的代码,所以Java 7借助了编译

    器来实现这项改进。

    这可以减少我们编写错误代码的几率。相比之下,想想你用Java 6写段代码,要从一个

    URL(url)中读取字节流,并把读取到的内容写入到文件(out)中,这么做很容易产生错

    误。代码清单1-3是可行方案之一。

    代码清单1-3 Java 6中的资源管理语法

    InputStream is = null;

    try {

    is = url.openStream;

    OutputStream out = new FileOutputStream(file);

    try {

    byte[] buf = new byte[4096];

    int len;

    while ((len = is.read(buf)) >= 0)

    out.write(buf, 0, len);

    } catch (IOException iox) { 处理异常(能读或写)

    } finally {

    try {

    out.close;

    } catch (IOException closeOutx) { 遇到异常也做不了什么

    }

    }

    } catch (FileNotFoundException fnfx) { 处理异常

    } catch (IOException openx) { 处理异常

    } finally {

    try {

    if (is != null) is.close;

    } catch (IOException closeInx) { 遇到异常也做不了什么

    }

    }

    看明白了吗?重点是在处理外部资源时,墨菲定律(任何事都可能出错)一定会生效,比

    如:

    URL中的InputStream无法打开,不能读取或无法正常关闭;

    OutputStream对应的File无法打开,无法写入或不能正常关闭;

    上面的问题同时出现。

    最后一种情况是最让人头疼的——异常的各种组合拳打出来令人难以招架。

    新语法能大大减少错误发生的可能性,这正是它受欢迎的主要原因。编译器不会犯开发人员

    编写代码时易犯的错误。

    让我们看看代码清单1-3中的代码用Java 7写出来什么样。和前面一样,url是一个指向下载

    目标文件的URL对象,file是一个保存下载数据的File对象。

    更多电子书下载就在www.aiyadu.com代码清单1-4 Java 7中的资源管理语法

    try (OutputStream out = new FileOutputStream(file);

    InputStream is = url.openStream ) {

    byte[] buf = new byte[4096];

    int len;

    while ((len = is.read(buf)) > 0) {

    out.write(buf, 0, len);

    }

    }

    这是资源自动化管理代码块的基本形式——把资源放在try的圆括号内。C程序员看到这个

    也许会觉得有点眼熟,是的,它的确很像C中的从句,带着这种理解使用这个新特性是个不

    错的起点。在这段代码块中使用的资源在处理完成后会自动关闭。

    但使用try-with-resources特性时还是要小心,因为在某些情况下资源可能无法关闭。比如在

    下面的代码中,如果从文件(someFile.bin)创建ObjectInputStream时出

    错,FileInputStream可能就无法正确关闭。

    try ( ObjectInputStream in = new ObjectInputStream(new

    FileInputStream(someFile.bin)) ) {...

    }

    假定文件(someFile.bin)存在,但可能不是ObjectInput类型的文件,所以文件无法正

    确打开。因此不能构建ObjectInputStream,所以FileInputStream也没办法关闭。

    要确保try-with-resources生效,正确的用法是为各个资源声明独立变量。

    try ( FileInputStream fin = new FileInputStream(someFile.bin);

    ObjectInputStream in = new ObjectInputStream(fin) ) {...

    }

    TWR的另一个好处是改善了错误跟踪的能力,能够更准确地跟踪堆栈中的异常。在Java 7之

    前,处理资源时抛出的异常信息经常会被覆盖。TWR中可能也会出现这种情况,因此Java 7

    对跟踪堆栈进行了改进,现在开发人员能看到可能会丢失的异常类型信息。

    比如在下面这段代码中,有一个返回InputStream的值为null的方法:

    try(InputStream i = getNullStream{

    i.available;

    }

    在改进后的跟踪堆栈中能看到提示,注意其中被抑制的NullPointerException(简称

    NPE):

    Exception in thread main java.lang.NullPointerException

    更多电子书下载就在www.aiyadu.com at wgjd.ch01.ScratchSuprExcep.run(ScratchSuprExcep.java:23)

    at wgjd.ch01.ScratchSuprExcep.main(ScratchSuprExcep.java:39)

    Suppressed:java.lang.NullPointerException

    at wgjd.ch01.ScratchSuprExcep.run(ScratchSuprExcep.java:24)

    1 more

    TWR与AutoCloseable

    目前TWR特性依靠一个新定义的接口实现AutoCloseable。TWR的try从句中出现的资源

    类都必须实现这个接口。Java 7平台中的大多数资源类都被修改过,已经实现了

    AutoCloseable(Java 7中还定义了其父接口Closeable),但并不是全部资源相关的类

    都采用了这项新技术。不过,JDBC 4.1已经具备了这个特性。

    然而在你自己的代码里,在需要处理资源时一定要用TWR,从而避免在异常处理时出现

    bug。

    希望你能尽快使用try-with-resources,把那些多余的bug从代码库中赶走。

    1.3.5 钻石语法

    针对创建泛型定义和实例太过繁琐的问题,Java 7做了一项改进,以减少处理泛型时敲键盘

    的次数。比如你用userid(整型值)标识一些user对象,每个user都对应一个或多个查找

    表1。这用代码应该如何表示呢?

    1 一种为提高处理速度而用查询取代计算的处理机制。一般是将事先计算好的结果存在数组或映射中,然后在需要该结果

    时直接读取,比如用三角表查某一角度的正弦值。——译者注

    Map> usersLists =

    new HashMap>;

    这简直太长了,并且几乎一半字符都是重复的。如果能写成

    Map> usersLists = new HashMap<>;

    让编译器推断出右侧的类型信息是不是更好?神奇的Coin项目满足了你这个心愿。在Java 7

    中,像这样的声明缩写完全合法,还可以向后兼容,所以当你需要处理以前的代码时,可以

    把过去比较繁琐的声明去掉,使用新的类型推断语法,这样可以省出点儿空间来。

    编译器为这个特性采用了新的类型推断形式。它能推断出表达式右侧的正确类型,而不是仅

    仅替换成定义完整类型的文本。

    为什么叫“钻石语法”

    把它称为”钻石语法”是因为这种类型信息看起来像钻石。原来提案中的名字是“为泛型实

    例创建而做的类型推断改进”(Improved Type Inference for Generic Instance Creation)。这

    个名字太长,可缩写ITIGIC听上去又很傻,所以干脆就叫钻石语法了。

    更多电子书下载就在www.aiyadu.com新的钻石语法肯定会让你少写些代码。我们最后还要探讨Coin项目中的一个特性——使用变

    参时的警告信息。

    1.3.6 简化变参方法调用

    这是所有修改里最简单的一个,只是去掉了方法签名中同时出现变参和泛型时才会出现的类

    型警告信息。

    换句话说,除非你写代码时习惯使用类型为T的不定数量参数,并且要用它们创建集合,否

    则你就可以进入下一节了。如果你想要写下面这种代码,那就继续阅读本节:

    public static Collection doSomething(T... entries) {...

    }

    还在?很好。这到底是怎么回事?

    变参方法是指参数列表末尾是数量不定但类型相同的参数方法。但你可能还不知道变参方法

    是如何实现的。基本上,所有出现在末尾的变参都会被放到一个数组中(由编译器自动创

    建),并作为一个参数传入。

    这是个好主意,但是存在一个公认的Java泛型缺陷——不允许创建已知类型的泛型数组。比

    如下面这段代码,编译就无法通过:

    HashMap[] arrayHm = new HashMap<>[2];

    不可以创建特定泛型的数组,只能这样写:

    HashMap[] warnHm = new HashMap[2];

    可这样编译器会给出一个只能忽略的警告。你可以将warnHm的类型定义

    为HashMap数组,但不能创建这个类型的实例,所以你不得不硬着头皮

    (或至少忘掉警告)硬生生地把原始类型(HashMap数组)的实例塞给warnHm。

    这两个特性(编译时生成数组的变参方法和已知泛型数组不能是可实例化类型)碰到一起

    时,会令人有点头疼。看看下面这段代码:

    HashMap hm1 = new HashMap<>;

    HashMap hm2 = new HashMap

    Collection> coll = doSomething(hm1,hm2);

    编译器会尝试创建一个包含hm1和hm2的数组,但这种类型的数组应该是被严格禁止使用

    的。面对这种进退两难的局面,编译器只好违心地创建一个本来不应出现的泛型数组实例,但它又觉得自己不能保持沉默,所以还得嘟囔着警告你这是“未经检查或不安全的操作”。

    从类型系统的角度看,这非常合理。但可怜的开发人员本想使用一个十分靠谱的API,一看

    更多电子书下载就在www.aiyadu.com到这些吓人的警告,却得不到任何解释,不免会内心忐忑。

    1.Java 7中的警告去了哪里

    Java 7的这个新特性改变了警告的对象。构建这些类型毕竟有破坏类型安全的风险,这总得

    有人知道。但 API 的用户对此是无能为力的,不管doSomething是不是干了坏事,破坏

    了类型安全,都不在API用户的控制范围之内。

    真正需要看到这个警告信息的是写doSomething的人,即API的创建者,而不是使用者。

    所以Java 7把警告信息从使用API的地方挪到了定义API的地方。

    过去是在编译使用API的代码时触发警告,而现在是在编译这种可能会破坏类型安全的API时

    触发。编译器会警告创建这种API的程序员,让他注意类型系统的安全。

    为了减轻API开发人员的负担,Java 7还提供了一个新注解java.lang.SafeVarargs。把这

    个注解应用到API方法或构造方法之中,则会产生类型警告。通过用@SafeVarargs对这种方

    法进行注解,开发人员就不会在里面进行任何危险的操作,在这种情况下,编译器就不会再

    发出警告了。

    2.类型系统的修改

    虽然把警告信息从一个地方挪到另一个地方不是改变游戏规则的语言特性,但也证明了我们

    之前提到的观点——Coin项目曾奉劝诸位贡献者远离类型系统,因为把这么一个小变化讲清

    楚要大费周章。这个例子表明搞清楚类型系统不同特性之间如何交互是多么费心费力,而且

    对语言的修改被实现后又会怎么影响这种交互。这还不是特别复杂的修改,更大的变动所涉

    及的内容还会更多,其中还包括大量微妙的分支。

    最后这个例子阐明了由小变化引发的错综复杂的影响。我们对Coin项目中改进的讨论也结束

    了。尽管它们几乎全都是语法上的小变化,但跟实现它们的代码量相比,它们所带来的正面

    影响还是很可观的。一旦开始使用,你就会发现这些特性对程序真的很有帮助!

    更多电子书下载就在www.aiyadu.com1.4 小结

    修改语言非常困难。而用类库实现新特性总是相对容易一些,当然并不是所有特性都能用类

    库实现。面对挑战时,语言设计师可能会做出一些比他们的预想更轻微、更保守的调整。

    现在,我们该去看看构成发布版本更重要的东西了,先从Java 7中某些核心类库的变化开

    始。我们的下一站是IO类库,那里可以说是发生了天翻地覆的变化。在此之前,希望你已

    经掌握了Java之前的版本处理IO的方法,因为Java 7中的这些类(有时候被称为NIO.2)是构

    建在之前框架基础之上的。

    如果你想看到更多关于TWR实战的例子,或者想要了解最新、高性能的IO类,那就赶快进

    入下一章吧!

    更多电子书下载就在www.aiyadu.com第2章 新IO

    内容提要

    Java 7的新IO API(即NIO.2)

    Path——基于文件和目录的IO新基础

    Files应用类及它的各种辅助方法

    如何实现常见的IO应用场景

    介绍异步IO

    本章重点是Java语言中改变较大的IO API,被称为“再次更新的IO”或NIO.2(即JSR-203)。

    NIO.2是一组新的类和方法,主要存在于java.nio包内。下面来看一下它的优点。

    它完全取代了java.io.File与文件系统的交互。

    它提供了新的异步处理类,让你无需手动配置线程池和其他底层并发控制,便可在后台

    线程中执行文件和网络IO操作。

    它引入了新的Network-Channel构造方法,简化了套接字(Socket)与通道的编码工

    作。

    先看案例。老板让你写个程序,要扫描生产服务器上的所有目录,找出曾经用各种读写和

    所有者权限写入过的所有properties文件。对于Java 6(及更低版本)而言,这几乎是不可能

    完成的任务,因为:

    没有直接支持目录树导航的类或方法;

    没办法检测和处理符号链接;1

    用简单操作读不出文件的属性(比如可读、可写或可执行)。

    1 符号链接是一种特殊类型的文件,指向文件系统中的另外一个文件或位置——你可以把它理解为快捷方式。

    用Java 7的NIO.2 API可以完成这个不可能的编程任务,它支持目录树的直接导航

    (Files.walkFileTree,2.3.1节)、符号链接(Files.isSymbolicLink,代码清单

    2-4),能用一行代码读取文件属性(Files.readAttributes,2.4.3节)。

    除此之外,老板还要求你在读取这些properties文件时不能打断主程序的处理流程。可最小的

    properties文件也有1MB,读取这些文件很可能打断程序的主流程!面对这一要求,在Java

    56的时代,你很可能会用java.util.concurrent包中的类创建线程池和工作线程队列,再用单独的后台线程读取文件。我们在第4章将会讨论到,现在Java中的并发仍然相当困

    难,并且非常容易出错。借助Java 7和NIO.2 API,你可以用新的

    AsynchronousFileChannel(2.5节),不用指定工作线程或队列就可以在后台读取大型文

    件。咻!

    这个新API非常有用,尽管它不能帮你冲咖啡,但它的发展趋势可在那儿摆着呢。

    更多电子书下载就在www.aiyadu.com第一个趋势是对其他数据存储方法的探索,特别是在非关系或大数据集领域。你可能很快就

    会遇到读写大文件(比如微博上的大型报告文件)的问题。NIO.2可以帮助你用一种异步、有效的方式读写大文件,还能利用底层操作系统的特性。

    第二个趋势是多核CPU的发展,使得真正并发且更快的IO成为可能。并发是个难以掌握的领

    域2,但NIO.2会助你一臂之力,它为多线程文件和套接字访问的应用提供了一个简单的抽象

    层。即便你不直接使用这些特性,它们也会对你的编程生涯产生极大影响,因为IDE、应用

    服务器和各种流行的框架会大量应用这些特性。

    2 第4章深入探讨了并发计算可能给你的编程生涯带来的微妙复杂性。

    这些只是NIO.2会对你有哪些帮助的例子。如果NIO.2可以解决你眼下面临的一些问题,本章

    的内容就是为你准备的!否则,你可以在接到Java IO 任务时再回来。

    本章你会体验到Java 7新IO的能力,以便你能够开始编写基于NIO.2的代码,并有信心探索

    新的API。除此之外,这些API还使用了一些第1章提到的特性,这证明Java 7确实会使用自己

    的特性。

    提示 将try-with-resources和NIO.2中的新API结合起来可以写出非常安全的IO程序,这在

    Java中还是破天荒的第一次!

    我们觉得你很可能会用到新的文件IO能力,所以本章会非常详细地介绍。你需要从了解新

    的文件系统抽象层开始,即先了解Path和它的辅助类。在Path之上,你会接触到常用的文

    件系统操作,比如复制和移动文件。

    我们还会向你介绍异步IO,给你看一个文件系统的例子。最后我们会讨论套接字和通道功

    能的融合,以及这对于网络应用开发人员意味着什么。但我们先来看一下NIO.2的由来。

    更多电子书下载就在www.aiyadu.com2.1 Java IO简史

    要品出NIO.2 API设计的真正味道,并深刻理解它们的用法,应该先弄清Java IO的历史。但

    我们非常理解你想要接触代码的渴望。别着急,你的这种渴望可以在2.2节得到满足。

    如果你发现某些API的用法非常简单甚至有点古怪,本节会帮助你从API设计者的角度看待

    NIO.2。这是Java IO的第三个主要版本,所以让我们回顾一下Java对IO支持的发展历史,看

    看NIO.2是怎么产生的。

    Java之所以能够广泛流传,其强大、丰富、简明的类库功不可没,编程时要解决的大多数问

    题几乎都可以在其中找到支持。但经验丰富的Java开发人员都知道,在老版本的Java中,有

    些地方不是那么给力。曾经让他们最崩溃的就是Java的输入输出(IO)API。

    2.1.1 Java 1.0到1.3

    在Java的早期版本(1.0~1.3)中没有完整的IO支持。也就是说开发人员在开发需要IO支持

    的应用时,要面临如下问题。

    没有数据缓冲区或通道的概念,开发人员要编程处理很多底层细节。

    IO操作会被阻塞,扩展能力受限。

    所支持的字符集编码有限,需要进行很多手工编码工作来支持特定类型的硬件。

    不支持正则表达式,数据处理困难。

    基本上,早期版本的Java缺乏对非阻塞IO的支持。开发人员万般无奈,只好自己实现可伸缩

    的IO解决方案。在Java 1.4发布之前,Java一直没能在服务器端开发领域得到重用,我们认

    为主要原因就是缺乏对非阻塞IO的支持。

    2.1.2 在Java 1.4中引入的NIO

    为了解决这些问题,Java开始实现对非阻塞IO的支持,与其他IO特性一起,帮助开发人员

    交付更快、更可靠的IO解决方案。其中有两次主要改进:

    在Java 1.4中引入非阻塞IO;

    在Java 7中对非阻塞IO进行修改。

    2002年发布Java 1.4时,非阻塞IO(NIO)以JSR-51的身份加入到Java语言中。下面这些特性

    就是那时增加的。自此之后,Java语言终于得到了服务器端开发人员的青睐:

    为IO操作抽象出缓冲区和通道层;

    字符集的编码和解码能力;

    提供了能够将文件映射为内存数据的接口;

    实现非阻塞IO的能力;

    更多电子书下载就在www.aiyadu.com基于流行的Perl实现的正则表达式类库。

    Perl——正则表达式之王

    毋庸置疑,Perl编程语言是正则表达式处理之王。实际上,它的设计和实现相当出色,甚

    至很多编程语言(包括Java)竞相模仿Perl的语法和语义。如果你对Perl语言感兴趣,可以

    访问http:www.perl.org一探究竟。

    NIO无疑使Java向前迈出了一大步,但IO编程对Java开发人员来说仍然是个挑战。特别是对

    于文件系统中的文件和目录处理而言,支持力度还是不够。那时的java.io.File类有些比

    较烦人的局限性。

    在不同的平台中对文件名的处理不一致。1

    没有统一的文件属性模型。(比如读写访问模型)

    遍历目录困难。

    不能使用平台操作系统的特性。2

    不支持文件系统的非阻塞操作。3

    1 一些批评者会说Java没有兑现“一次编写,处处运行”的承诺。

    2 人们通常希望能够使用LinuxUNIX中的符号链接机制。

    3 Java 1.4的确支持网络套接字的非阻塞操作。

    2.1.3 下一代IO-NIO.2

    为了突破这些局限性,同时也为了支持现代硬件和软件IO的新范例,由阿兰?波特曼主导的

    JSR-203应运而生。JSR-203最终变成了我们在Java 7中见到的NIO.2 API。它有三个主要目

    标,JSR-203规范中的第2.1节对它们进行了详细的介绍(http:jcp.orgenjsrdetail?

    id=203)。

    1. 一个能批量获取文件属性的文件系统接口,去掉和特定文件系统相关的API,还有一个

    用于引入标准文件系统实现的服务提供者接口。

    2. 提供一个套接字和文件都能够进行异步(与轮询、非阻塞相对)IO操作的API。

    3. 完成JSR-51中定义的套接字——通道功能,包括额外对绑定、选项配置和多播数据报的

    支持。

    接下来让我们从新文件系统的基础Path和它的朋友们开始吧!

    更多电子书下载就在www.aiyadu.com2.2 文件IO的基石:Path

    在NIO.2的文件IO中,Path是必须掌握的关键类之一。Path通常代表文件系统中的位置,比

    如C:\workspace\java7developer(Windows文件系统中的目录)或usrbinzip(nix文件系统中

    zip程序的位置)。如果你能理解如何创建和处理路径,就能浏览任何类型的文件系统,包

    括zip归档文件系统。

    我们通过图2-1(基于本书中的源码布局)来复习一下文件系统中的几个概念。

    目录树

    根路径

    绝对路径

    相对路径

    图2-1 说明根目录、绝对路径和相对路径概念的目录树

    之所以要讨论绝对路径和相对路径,是因为我们需要考虑程序运行的位置。比如说,有个程

    序可能在java7developersrctest目录下运行,代码要读取位于java7developersrcmain目录下

    更多电子书下载就在www.aiyadu.com的文件名。为了进入java7developersrcmain目录,可以用相对路径..main。

    但如果程序运行在java7developersrctestjavacomjava7developer,用相对路径 ..main则无法

    进入目标目录,而会进到并不存在的目录java7developersrctestjavacommain中。所以必须

    使用绝对路径,比如java7developersrcmain。

    反之亦然,你的程序可能一直运行在同一位置,比如图2-1中的target目录。但目录树的根目

    录可能会变,比如从java7developer变成了D:\workspace\j7d。这时就不能依靠绝对路径,而

    要用相对路径转到你想到达的位置。

    NIO.2中的Path是一个抽象构造。你所创建和处理的Path可以不马上绑定到对应的物理位置

    上。尽管看起来奇怪,但有时确实需要如此。比如说,你想创建一个Path来表示即将创建

    的新文件。在调用Files.createFile(Path target)1之前,这个文件是不存在的。如果

    在Path所对应的文件创建之前,你试图读取这个文件中的内容,就会导致IOException。

    如果你指定了一个并不存在的Path并试图用Files.readAllBytes(Path)之类的方法读

    取,结果是一样的。简言之,JVM只会把Path绑定到运行时的物理位置上。

    1 你一会儿就能在2.4节见到这个方法!

    警告

    在编写与具体文件系统相关的代码时要小心。创建Path为C:\workspace\java7developer,然

    后试图读取它的程序,这只能在有C:\workspace\java7developer位置的计算机上才能工作。

    一定要确保程序逻辑和异常处理考虑到了各种情况,包括可能在不同的文件系统上运行,或文件系统的结构可能被改动。本书的作者之一过去就犯过这个错误,结果把计算机系的

    一组硬盘全搞坏了!2

    2 我们不会告诉你是谁,不过欢迎你把他查出来!

    还是得再重复一遍,NIO.2把位置(由Path表示)的概念和物理文件系统的处理(比如复制

    一个文件)分得很清楚,物理文件系统的处理通常是由Files辅助类实现的。

    有关Path类以及将在本节讨论的其他类的进一步细节请见表2-1。

    表2-1 学习文件IO的关键基础类

    类 说明

    Path

    Path类中的方法可以用来获取路径信息,访问该路径中的各元素,将路径转换为其他形式,或提取路

    径中的一部分。有的方法还可以匹配路径字串以及移除路径中的冗余项

    Paths

    工具类,提供返回一个路径的辅助方法,比如get(String first, String... more)和get(URI

    uri)

    FileSystem 与文件系统交互的类,无论是默认的文件系统,还是通过其统一资源标识(URI)获取的可选文件系统

    FileSystems 工具类,提供各种方法,比如其中用于返回默认文件系统的FileSystems.getDefault

    记住,Path不一定代表真实的文件或目录。你可以随心所欲地操作Path,用Files中的功能

    更多电子书下载就在www.aiyadu.com来检查文件是否存在,并对它进行处理。

    提示 Path并不仅限于传统的文件系统,它也能表示zip或jar这样的文件系统。

    我们来完成几个简单的任务,探索一下Path类:

    创建一个Path;

    获取Path的相关信息;

    移除Path中的冗余项;

    转换一个Path;

    合并两个Path,在两个Path中间创建一个Path,并对这两个Path进行比较。

    我们先从创建表示文件系统中某个位置的Path开始。

    2.2.1 创建一个Path

    创建Path没什么难的。调用Paths.get(String first,String...more)是最快捷的做

    法,其中第二个变量一般用不到,它仅仅用来把额外的字符串合并起来形成Path字符串。

    提示 在NIO.2的API中,Path或Paths中的各种方法抛出的受检异常只有IOException。

    我们认为这虽然简单,但有时却会掩藏潜在的问题,而且如果你想处理IOException的某

    个显式子类,则需要额外编写异常处理代码。

    我们用Paths.get(String first)方法为usrbin目录下的文件压缩工具zip创建一个绝对

    Path。

    Path listing=Paths.get(usrbinzip);

    调用Paths.get(usrbinzip)的效果和调用下面这个长一点的代码效果一样:

    Path listing=FileSystems.getDefault.getPath(usrbinzip);

    提示

    创建Path时可以用相对路径。比如,运行在opt目录下的程序可以用..usrbinzip创建一个

    指向usrbinzip的Path。其含义是指进入opt的上一层目录(即),然后进入usrbinzip。

    通过调用toAbsolutePath方法,很容易把这个相对路径转换成绝对路径,如:listing.toAbsolutePath。

    你可以从Path中获取信息,比如其父目录、文件名(如果有的话)等等。

    2.2.2 从Path中获取信息

    Path类中有一组方法可以返回你正在处理的路径的相关信息。代码清单2-1为usrbin目录下

    更多电子书下载就在www.aiyadu.com的zip工具创建一个Path,并输出相关信息,包括它的根目录和父目录。如果你的操作系统

    中zip也放在usrbin目录下,应该能见到下面这种输出信息。

    File Name [zip]

    Number of Name Elements in the Path [3]

    Parent Path [usrbin]

    Root of Path []

    Subpath from Root, 2 elements deep [usrbin]

    你在计算机上看到的结果和你所用的操作系统及程序运行的位置有关。

    代码清单2-1 从Path中获取信息

    import java.nio.file.Path;

    import java.nio.file.Paths;

    public class Listing_2_1 {

    public static void main(String[] args) {

    Path listing = Paths.get(usrbinzip);创建绝对路径

    System.out.println(File Name [ +

    listing.getFileName + ]);获取文件名

    ① 获取名称元素的数量

    System.out.println(Number of Name Elements

    in the Path [ +

    listing.getNameCount + ]);

    ② 获取Path的信息

    System.out.println(Parent Path [ +

    listing.getParent + ]);

    System.out.println(Root of Path [ +

    listing.getRoot + ]);

    System.out.println(Subpath from Root,2 elements deep [ +

    listing.subpath(0, 2) + ]);

    }

    }

    在创建了usrbinzip的Path之后,你可以看一下组成Path的元素个数,在此例中就是目录的

    数量①。相对其父目录和根目录,Path的位置会更有用。也可以通过指定起始和终止的索

    引来挑出子路径。在本例中是从Path的根(0)到其第二个元素(2)之间的子路径②。

    如果这是你初次接触NIO.2文件API,这些方法对你来说非常重要,因为你可以借助它们查看

    路径处理的结果。

    2.2.3 移除冗余项

    在编写工具软件,比如属性文件分析器时,需要处理的Path中可能会有一个或两个点:

    更多电子书下载就在www.aiyadu.com. 表示当前目录;.. 表示父目录。

    假设你的程序在java7developersrcmainjavacomjava7developerchapter2目录下运行(见图

    2-1)。你所在的目录和Listing_2_1.java一样,如果传给你的Path

    是.Listing_2.1.java,其中的.部分,即程序正在运行的目录,实际上并没什么用。在

    这里用短一点儿的Path——Listing_2_1.java就够了。

    Path中可能还有其他冗余项,比如符号链接(见2.4.3节)。假设在nix系统中usrlogs目录

    下,你想寻找日志文件log1.txt,但其实usrlogs只是一个指向applicationlogs的符号链接,那

    里才是存放日志文件的真正位置。要得到这个位置,就需要去掉冗余的符号信息。

    所有这些冗余项都会导致Path指向的不是你认为它应该指向的位置。

    在Java 7中,有两个辅助方法可以用来弄清Path的真实位置。首先可以用normalize方法

    去掉Path中的冗余信息。下面的代码会返回Listing_2_1.java的Path,去掉了表明它在当前目

    录(.部分)中的冗余符号。

    Path normalizedPath = Paths.get(.Listing_2_1.java).normalize;

    此外,toRealPath方法也很有效,它融合了toAbsolutePath和normalize两个方

    法的功能,还能检测并跟随符号连接。

    还是回到nix系统中的日志文件那个例子,在usrlogs目录下有个日志文件log1.txt,而这个目

    录实际上是指向applicationlogs的符号链接。通过调用toRealPath,你能得到表

    示applicationlogslog1.txt的真正Path。

    Path realPath = Paths.get(usrlogslog1.txt).toRealPath;

    我们要讨论的最后一个Path API特性是比较多个Path,并找出它们之间的Path。

    2.2.4 转换Path

    转换路径最多的是工具软件。比如你可能需要比较文件之间的关系,以便了解源码目录树的

    结构是否符合编码规范。或者在shell脚本中执行程序时可能会传入一些Path参数,并需要把

    这些参数变成有意义的Path。在NIO.2里可以很容易地合并Path,在两个Path中再创建

    Path或对Path进行比较。

    下面的代码将两个Path合并,通过调用resolve方法,将uat和confapplication.properties合并

    成表示uatconfapplication.properties的完整Path。

    Path prefix = Paths.get(uat);

    Path completePath = prefix.resolve(confapplication.properties);

    要取得两个Path之间的路径,可以用relativize(Path)方法。下面的代码计算了从日志目

    更多电子书下载就在www.aiyadu.com录到配置目录之间的路径。

    String logging = args[0];

    String configuration = args[1];

    Path logDir = Paths.get(logging);

    Path confDir = Paths.get(configuration);

    Path pathToConfDir = logDir.relativize(confDir);

    如你所愿,你可以用startsWith(Path prefix)、equals(Path path)等值比较

    或endsWith(Path suffix)来对路径进行比较。

    现在使用Path类对你来说应该没有问题了,但Java 7之前版本的那些代码怎么办呢?负责

    NIO.2的团队考虑到了向后兼容性,所以加了两个新的API特性来保证基于Path的新IO和老

    版本代码之间的互操作性。

    2.2.5 NIO.2 Path和Java已有的File类

    新API中的类可以完全替代过去基于java.io.File的API。但你不可避免地要和大量遗留下

    来的IO代码交互。Java 7提供了两个新方法:

    java.io.File类中新增了toPath方法,它可以马上把已有的File转化为新的

    Path。

    Path类中有个toFile方法,它可以马上把已有的Path转化为File。

    下面的代码演示了这一功能。

    File file = new File(..Listing_2_1.java);

    Path listing = file.toPath;

    listing.toAbsolutePath;

    file = listing.toFile;

    现在,我们完成了对Path类的探索。接着要研究一下Java 7对目录处理的支持,特别是对目

    录树。

    更多电子书下载就在www.aiyadu.com2.3 处理目录和目录树

    在读2.2节关于路径的内容时,你可能已经猜到目录不过是带有特别属性的Path。遍历目录

    的能力是Java 7引人注目的新特性。新加入的java.nio.file.DirectoryStream接口和

    它的实现类提供了很多功能:

    循环遍历目录中的子项,比如查找目录中的文件;

    用glob表达式1(比如Foobar)进行目录子项匹配和基于MIME的内容检测(比如

    textxml文件);

    用walkFileTree方法实现递归移动、复制和删除操作。

    本节主要讨论两个常见用例:在一个目录中查找文件以及在目录树中执行相同的任务。我们

    先从最简单的开始:在一个目录中查找任意文件。

    1 glob通常指有限的模式匹配,源自早期Unix中的glob库函数,该函数用于查找文件系统中指定模式的路径,虽然所用语法

    和正则表达式类似,但没有正则表达式那么强大的表达力,详见附录B。——译者注

    2.3.1 在目录中查找文件

    我们先讨论一个简单的例子,用模式匹配过滤出java7developer项目中所有的.properties文

    件。请看下面的代码:

    代码清单2-2 列出目录下的properties文件

    Path dir = Paths.get(C:\\workspace\\java7developer);①设定起始路径

    try(DirectoryStream stream

    = Files.newDirectoryStream(dir, .properties)){②声明过滤流

    ③ 找出所有.properties 文件并输出

    for (Path entry: stream)

    {

    System.out.println(entry.getFileName);

    }

    }

    catch (IOException e)

    {

    System.out.println(e.getMessage);

    }

    最前面是我们已经熟悉的Paths.get(String)调用①。紧随其后的是关键方法

    DirectoryStream(Path directory, String patternMatch)②,它返回一个经过过滤

    的DirectoryStream,其中包含以.properties结尾的文件。最后输出每个子项③。

    过滤流中用到的模式匹配称为glob模式匹配,它和Perl正则表达式类似,但稍有不同。附录

    B中有如何使用glob模式匹配的详细说明。

    更多电子书下载就在www.aiyadu.com代码清单2-2展示了新API处理单个目录的能力,但如果需要递归过滤多个目录时该怎么办?

    2.3.2 遍历目录树

    Java 7支持整个目录树的遍历。也就是说你可以很容易地搜寻目录树中的文件,在子目录中

    查找,并对它们执行操作。比如你可能想在做开发的机器上弄个工具类来删除目

    录optworkspacejava下的所有.class文件,完成构建前的清除工作。

    遍历目录树是Java 7的新特性,要想正确使用,你得掌握一些接口及其实现的细节。其中的

    关键方法是:

    Files.walkFileTree(Path startingDir, FileVisitor visitor);

    提供startingDir非常简单,但给出FileVisitor接口的实现类就比较麻烦了(参

    数FileVisitor visitor看上去就不是善茬儿),因为最少得实现下面5个

    方法(T一般就是Path):

    FileVisitResult preVisitDirectory(T dir)

    FileVisitResult preVisitDirectoryFailed(T dir, IOException exc)

    FileVisitResult visitFile(T file, BasicFileAttributes attrs)

    FileVisitResult visitFileFailed(T file, IOException exc)

    FileVisitResult postVisitDirectory(T dir, IOException exc)

    看起来挺吓人的吧?好在Java 7 API的设计者们已经提供了一个默认实现

    类,SimpleFileVisitor

    我们要扩展并修改代码清单2-2。下面的代码会列出C:\workspace\java7developer\src目录下及

    其子目录内的所有.java文件。这段代码展示了Files.walkFileTree方法对默认实现

    类SimpleFileVisitor的用法,用一个特定的visitFile方法实现来改进它。

    代码清单2-3 列出子目录下的所有java源码文件

    public class Find

    {

    public static void main(String[] args) throws IOException

    {

    Path startingDir =

    Paths.get(C:\\workspace\\java7developer\\src);设置起始目录

    Files.walkFileTree(startingDir,new FindJavaVisitor); ①调用walkFileTree

    }

    private static class FindJavaVisitor

    extends SimpleFileVisitor ②扩展SimpleFileVisitor

    {

    @Override

    public FileVisitResult

    更多电子书下载就在www.aiyadu.com visitFile(Path file, BasicFileAttributes attrs) ③ 重写visitFile方法

    {

    if (file.toString.endsWith(.java)) {

    System.out.println(file.getFileName);

    }

    return FileVisitResult.CONTINUE;

    }

    }

    }

    整个过程从调用Files.walkFileTree方法开始①。这里的关键是FindJavaVisitor,该类

    扩展了FileVisitor的默认实现类SimpleFileVisitor②。你想让SimpleFileVisitor来

    完成大部分工作,比如遍历目录。可实际上你唯一要做的就是重写

    visitFile(Path,BasicFileAttributes)1方法③,在这个方法中你也只需要写些代码来

    判断文件名是否以.java结尾,如果确实是,就在stdout中输出。

    1 2.4节会介绍BasicFileAttributes,所以暂时不用管它。

    其他用例包括递归移动、复制、删除或者修改文件。在大多数应用场景中,你只需要扩

    展SimpleFileVisitor。但如果你想实现自己的FileVisitor,API也很灵活。

    注意

    为了确保递归等操作的安全性,walkFileTree方法不会自动跟随符号链接。如果你确实

    需要跟随符号链接,就需要检查那个属性(如2.4.3节所述)并执行相应的操作。

    现在你对路径和目录树已经熟悉了,该从处理位置进入真正的文件系统操作上了,接下来我

    们会向你介绍新的Files类及它的朋友们。

    更多电子书下载就在www.aiyadu.com2.4 NIO.2的文件系统IO

    对于文件系统的操作任务,比如移动文件、修改文件属性,以及处理文件内容等,在NIO.2

    中都有所改善。对这些操作的支持主要是由Files类提供的。

    表2-2中有关于Files类的详细介绍,此外本节还会介绍另外一个也很重要的

    类:WatchService。

    表2-2 文件处理的基础类

    类 说明

    Files 让你轻松复制、移动、删除或处理文件的工具类,有你需要的所有方法

    WatchService 用来监视文件或目录的核心类,不管它们有没有变化

    在本节中,你将学会如何在文件和文件系统上执行下面这些任务:

    创建和删除文件;

    移动、复制、重命名和删除文件;

    文件属性的读写;

    文件内容的读取和写入;

    处理符号链接;

    用WatchService发出文件修改通知;

    使用SeekableByteChannel——一个可以指定位置及大小的增强型字节通道。

    这看起来可能挺恐怖的,但由于设计巧妙,API提供了很多辅助方法,把抽象层隐藏了起

    来,让你可以轻松快捷地处理文件系统。

    警告 NIO.2 API对原子操作的支持有很大改进,但涉及文件系统处理时,仍然主要依靠

    代码来提供保护。即使是执行了一半的操作,也很可能会因为突然断网、咖啡泼到服务器

    上,或某个冒失鬼在错误的UNIX机器上执行了shutdown now命令(本书作者之一亲身经

    历的著名事件)等诸多原因而出错。尽管API的某些方法还是会偶尔抛出

    个RuntimeException,但某些异常状况可以由Files.exists(Path)这样的辅助方法来

    缓解。

    学习新API最好的办法就是读写代码。接下来我们来看一些实际案例,先从基本的文件创建

    和删除开始。

    2.4.1 创建和删除文件

    只需要调用Files类里的辅助方法,就可以很容易地创建和删除文件。当然,你接到的任务

    不可能总像默认情况那么简单,所以我们额外加了一些选项,比如在新创建的文件上设定可

    读可写可执行的安全访问权限。

    更多电子书下载就在www.aiyadu.com提示 如果你要在自己的机器上运行本节中的代码,请用实际路径替换掉代码中的路径。

    下面的代码展示了基本的文件创建操作,用到了Files.createFile(Path target)方法。

    如果你的操作系统里有个D:\Backup目录,运行代码之后就会在那里创建一个MyStuff.txt文

    件。

    Path target = Paths.get(D:\\Backup\\MyStuff.txt);

    Path file = Files.createFile(target);

    通常出于安全考虑,要定义所创建的文件是用于读、写、执行,或三者权限的某种组合时,你要指明该文件的某些FileAttributes。因为这取决于文件系统,所以需要使用与文件系

    统相关的文件权限类。

    下面是一个在POSIX文件系统1上为属主、属主组内用户和所有用户设置读写许可的例子。

    这种方法允许所有用户对即将创建的文件D:\Backup\MyStuff.txt进行读写操作。

    1 可移植操作系统接口(UNIX),是一种许多操作系统都支持的基本标准。

    Path target = Paths.get(D:\\Backup\\MyStuff.txt);

    Set perms =

    PosixFilePermissions.fromString(rw-rw-rw-);

    FileAttribute> attr =

    PosixFilePermissions.asFileAttribute(perms);

    Files.createFile(target, attr);

    在java.nio.file.attribute包里有一大串已经写好的FilePermission类。对文件属性

    的支持在2.4.3节中还有更详细的论述。

    警告 如果在创建文件时要指定访问许可,不要忽略其父目录强加给该文件的umask限制

    或受限许可。比如说,你会发现即便你为新文件指定了rw-rw-rw许可,但由于目录的掩

    码,实际上文件最终的访问许可却是rw-r--r--。

    删除文件要简单一些,可以用Files.delete(Path)方法。下面的代码删除了刚刚创建的

    D:\Backup\MyStuff.txt文件。当然,运行这个Java程序的用户需要有删除文件的权限。

    Path target = Paths.get(D:\\Backup\\MyStuff.txt);

    Files.delete(target);

    接下来你将学到如何在文件系统中复制和移动文件。

    2.4.2 文件的复制和移动

    使用Files类中简单的辅助方法可以很轻松地完成文件的复制和移动。

    下面的代码演示了如何用Files.copy(Path source, Path target)方法完成基本的复制

    操作。

    更多电子书下载就在www.aiyadu.comPath source = Paths.get(C:\\My Documents\\Stuff.txt);

    Path target = Paths.get(D:\\Backup\\MyStuff.txt);

    Files.copy(source, target);

    复制文件时通常需要设置某些选项。下面这个例子用到了覆盖即替换已有文件的选项。

    import static java.nio.file.StandardCopyOption.;

    Path source = Paths.get(C:\\My Documents\\Stuff.txt);

    Path target = Paths.get(D:\\Backup\\MyStuff.txt);

    Files.copy(source, target, REPLACE_EXISTING);

    其他的复制选项包括COPY_ATTRIBUTES(复制文件属性)和ATOMIC_MOVE(确保在两边的

    操作都成功,否则回滚)。

    移动和复制很像,都是用原子Files.move(Path source, Path target)方法完成的。通

    常在移动文件时,你想要用复制选项,此时便可以用Files.move(Path source, Path

    target, CopyOptions...)方法,但要注意变参的使用。

    在下面这个例子中,我们要在移动源文件时保留其属性,并且覆盖目标文件(如果存在的

    话)。

    import static java.nio.file.StandardCopyOption.;

    Path source = Paths.get(C:\\My Documents\\Stuff.txt);

    Path target = Paths.get(D:\\Backup\\MyStuff.txt);

    Files.move(source, target, REPLACE_EXISTING, COPY_ATTRIBUTES);

    现在你已经能创建、删除、复制和移动文件了,下面该认真研究一下Java 7对文件属性的支

    持了。

    2.4.3 文件的属性

    文件属性控制着谁能对文件做什么。一般情况下,做什么许可包括能否读取、写入或执行

    文件,而由谁许可包括属主、群组或所有人。

    本节从讨论文件的基本属性组开始,比如文件最后访问时间以及它是目录还是符号链接等。

    本节的第二部分讨论对特定文件系统的文件属性的支持,因为不同的文件系统都有它们自己

    的属性集和属性含义的解释,所以这部分比较难。

    让我们先从了解Java 7 对基本文件属性的支持开始吧。

    1. 基本文件属性支持

    更多电子书下载就在www.aiyadu.com真正通用的文件属性并不多,但确实有一组大多数文件系统都支持的属性。接口

    BasicFileAttributes定义了这个通用集,但实际上工具类Files就可以回答与文件相关

    的各种问题,比如下面这些:

    最后修改时间是什么时候?

    它有多大?

    它是符号连接吗?

    它是目录吗?

    代码清单2-4说明了Files类中用于收集这些基本文件属性的方法。代码输出了usrbinzip的

    相关信息,你看到的输出应该和下面的类似:

    usrbinzip

    2011-07-20T16:50:18Z

    351872

    false

    false

    {lastModifiedTime=2011-07-20T16:50:18Z,fileKey=(dev=e000002,ino=30871217), isDirectory=false,lastAccessTime=2011-06-13T23:31:11Z, isOther=false,isSymbolicLink=false, isRegularFile=true,creationTime=2011-07-20T16:50:18Z, size=351872}

    注意,所有这些属性都是调用Files.readAttributes(Path path, Stringattributes,LinkOption... options)得到的。代码清单2-4如下所示:

    代码清单2-4 通用的文件属性

    try

    {

    Path zip = Paths.get(usrbinzip);获取Path

    输出属性

    System.out.println(Files.getLastModifiedTime(zip));

    System.out.println(Files.size(zip));

    System.out.println(Files.isSymbolicLink(zip));

    System.out.println(Files.isDirectory(zip));

    System.out.println(Files.readAttributes(zip, ));执行批量读取

    }

    catch (IOException ex)

    {

    System.out.println(Exception [ + ex.getMessage + ]);

    }

    还有一些可以从Files类的方法中采集到的通用文件属性信息。这样的信息包括文件属主,是否为符号链接等。请参照Files类的Javadoc查看完整的辅助方法列表。

    Java 7也支持跨文件系统的文件属性查看和处理功能。

    更多电子书下载就在www.aiyadu.com2. 特定文件属性支持

    在2.4.1节创建文件时你已经见过FileAttribute接口和PosixFilePermissions类了。为了

    支持文件系统特定的文件属性,Java 7允许文件系统提供者实现FileAttributeView和

    BasicFileAttributes接口。

    警告 我们之前已经说过了,但有必要再重复一次。在编写特定文件系统的代码时一定要

    小心。一定要确保你的逻辑和异常处理考虑到了代码在不同文件系统上运行的情况。

    来看一个例子,其中你想用Java 7保证正确的访问许可被设置在特定文件中。图2-2显示了

    Admin用户的home目录的列表。注意那个特殊的.profile隐藏文件,它只允许Admin用户写,其他任何人都没有写权限,但所有人都可以读取该文件。

    图2-2 Admin用户的home目录列表,显示.profile的访问许可

    在下面的代码中,你要确保.profile文件的访问许可设置正确,与图2-2对应。Admin用户希望

    其他所有用户都可以读取该文件,但只有他自己来写。你可以用特定的POSIX

    PosixFilePermission和PosixFileAttributes类来保证访问许可(rw-r--r--)是正确

    的。

    代码清单2-5 Java 7对文件属性的支持

    import static java.nio.file.attribute.PosixFilePermission.;

    try

    {

    Path profile = Paths.get(userAdmin.profile);

    PosixFileAttributes attrs =

    Files.readAttributes(profile,PosixFileAttributes.class);①获取属性视图

    Set posixPermissions =

    attrs.permissions;②读取访问许可

    posixPermissions.clear; ③清除访问许可

    日志信息

    String owner = attrs.owner.getName;

    String perms =

    PosixFilePermissions.toString(posixPermissions);

    更多电子书下载就在www.aiyadu.com System.out.format(%s %s%n, owner, perms);

    ④设置新的访问许可

    posixPermissions.add(OWNER_READ);

    posixPermissions.add(GROUP_READ);

    posixPermissions.add(OTHER_READ);

    posixPermissions.add(OWNER_WRITE);

    Files.setPosixFilePermissions(profile, posixPermissions);

    }

    catch(IOException e)

    {

    System.out.println(e.getMessage);

    }

    代码从导入PosixFilePermission常量还有其他未显示的导入开始,然后得到.profile文件

    的Path。Files类中有个辅助方法让你可以读取特定文件系统的属性,在这个例子中

    是PosixFileAttributes①。然后你就可以访问PosixFilePermission②。在清除了已有

    的许可之后③,你可以为文件添加新的访问许可,当然还是用Files中的方法④。

    你可能已经注意到了,PosixFilePermission是一个enum,因此没有实现

    FileAttributeView接口。为什么这里没用PosixFileAttributeView呢?实际上

    是Files辅助类把它隐藏了起来,这样你就可以用readAttributes方法直接读取文件属性

    了,也可以用setPosixFilePermissions方法直接设置访问许可。

    除了基本属性,Java 7还有一个用来支持特别操作系统特性的扩展系统。可惜,我们不可能

    囊括所有特殊情况,但我们会给你看一个扩展系统的例子:Java 7对符号链接的支持。

    3. 符号链接

    你可以把符号链接看做指向另一个文件或目录的入口,并且在大多数情况下它们都是透明

    的。比如切换到符号链接的目录下会把你带到符号链接所指向的目录下。但在写软件时,比

    如备份工具或部署脚本,你需要慎重考虑是否应该跟随符号链接,NIO.2允许你做出选择。

    我们再用一下2.2.3节的例子。你要在nix系统上查询usrlogs目录下的日志文件log1.txt的信

    息。但usrlogs目录实际上是一个指向applicationlogs目录的符号链接(指

    针),applicationlogs目录才是日志文件的真正位置。

    符号链接在宿主操作系统中使用,包括(但不限于)UNIX、Linux、Windows 7和Mac OS

    X。Java 7对符号链接的支持遵循UNIX操作系统中实现的语义。

    下面的代码在读取基本文件属性之前先检查指向安装Java的 optplatform目录的Path,看它

    是否为符号链接,我们想读取文件真正位置的属性。代码清单2-6如下所示:

    代码清单2-6 探索符号链接

    Path file = Paths.get(optplatformjava);

    try

    更多电子书下载就在www.aiyadu.com{

    if(Files.isSymbolicLink(file)) ① 检查符号链接

    {

    file = Files.readSymbolicLink(file); ②读取符号链接

    }

    Files.readAttributes(file, BasicFileAttributes.class);③ 读取文件属性

    }

    catch (IOException e)

    {

    System.out.println(e.getMessage);

    }

    Files类提供了一个isSymbolicLink(Path)方法来检查符号链接①。它还有一个辅助方

    法,可以用于返回符号链接目标的真实Path②,所以你能读到正确的文件属性③。

    NIO.2 API默认会跟随符号链接。如果不想跟随,需要用LinkOption.NOFOLLOW_LINKS选

    项。这一选项可以用在几个方法调用上。如果你要读取符号链接本身的基本文件属性,应该

    调用:

    Files.readAttributes(target,BasicFileAttributes.class,LinkOption.NOFOLLOW_LINKS);

    符号链接是Java 7对特定文件系统支持最常用的例子,API设计者也考虑到了未来对特定文件

    系统支持特性的扩展,比如量子加密文件系统。

    你已经做过文件处理了,现在可以开始研究对文件内容的处理了。

    2.4.4 快速读写数据

    Java 7可以尽可能多地提供用来读取和写入文件内容的辅助方法。当然,这些新方法使

    用Path,但它们也可以与那些java.io包里基于流的类进行互操作。因此,你用一个方法就

    可以读取文件中的所有行或全部字节。

    本节会向你介绍打开文件(带选项)的过程,以及一小组常用的文件读写例子。让我们先

    从打开文件的不同方式开始。

    1. 打开文件

    Java 7可以直接用带缓冲区的读取器和写入器或输入输出流(为了和以前的Java IO代码兼

    容)打开文件。下面的代码演示了Java 7如何用Files.newBufferedReader方法打开文件

    并按行读取其中的内容。

    Path logFile = Paths.get(tmpapp.log);

    try (BufferedReader reader = Files.newBufferedReader(logFile, StandardCharsets.UTF_8)) {

    String line;

    更多电子书下载就在www.aiyadu.com while ((line = reader.readLine!= null) {...

    }

    }

    打开一个用于写入的文件也很简单。

    Path logFile = Paths.get(tmpapp.log);

    try (BufferedWriter writer =

    Files.newBufferedWrite(logFile, StandardCharsets.UTF_8, StandardOpenOption.WRITE)) {

    writer.write(Hello World!);..

    }

    注意StandardOpenOption.WRITE选项的使用,这是可以添加的几个OpenOption变参之

    一。它可以确保写入的文件有正确的访问许可。其他常用的文件打开选项还有READ和

    APPEND。

    与InputStream和OutputStream的交互是通过

    Files.newInputStream(Path,OpenOption...) 和

    Files.newOutputStream(Path,OpenOption...)实现的。它们为过去基于java.io包的

    IO和新的基于java.nio包的文件IO之间架起了一座桥梁。

    提示 在处理String时,不要忘了查看它的字符编码。忘记设置字符编码(通过

    StandardCharsets类,比如new String(byte[],StandardCharsets.UTF_8)) 可能导

    致不可预料的字符编码问题。

    前面的代码片段还是用Java 6及之前版本编写的读取和写入文件代码,仍然属于比较繁琐的

    底层代码。而Java 7具备更高层的抽象能力,可以帮你避免很多不必要的繁琐编码工作。

    2.简化读取和写入

    辅助类Files有两个辅助方法,用于读取文件中的全部行和全部字节。也就是说你没必要再

    用while循环把数据从字节数组读到缓冲区 ......

您现在查看是摘要介绍页, 详见PDF附件(5888KB,518页)