diff --git a/README.md b/README.md index 9d501d37..e6c333a9 100644 --- a/README.md +++ b/README.md @@ -115,10 +115,6 @@ 如不熟悉 md 排版,可不必纠结,我会在合并 pr 时代为排版 如还有其它问题,欢迎发送 issue,谢谢~ -## 友情链接 - -[Effective.Java.3rd.Edition 中文版](https://sjsdfg.github.io/effective-java-3rd-chinese/#/) - ## 开源协议 本项目基于 MIT 协议开源。 diff --git a/SUMMARY.md b/SUMMARY.md index a15b48fc..c7d1abb2 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,414 +1,448 @@ -# Summary - -* [Introduction](README.md) -* [译者的话](docs/README.md) -* [封面](docs/book/00-On-Java-8.md) -* [前言](docs/book/00-Preface.md) - * [教学目标](docs/book/00-Preface.md#教学目标) - * [语言设计错误](docs/book/00-Preface.md#语言设计错误) - * [测试用例](docs/book/00-Preface.md#测试用例) - * [普及性](docs/book/00-Preface.md#普及性) - * [关于安卓](docs/book/00-Preface.md#关于安卓) - * [电子版权声明](docs/book/00-Preface.md#电子版权声明) - * [版本说明](docs/book/00-Preface.md#版本说明) - * [封面设计](docs/book/00-Preface.md#封面设计) - * [感谢的人](docs/book/00-Preface.md#感谢的人) - * [献礼](docs/book/00-Preface.md#献礼) -* [简介](docs/book/00-Introduction.md) - * [前提条件](docs/book/00-Introduction.md#前提条件) - * [JDK文档](docs/book/00-Introduction.md#JDK文档) - * [C编程思想](docs/book/00-Introduction.md#C编程思想) - * [源码下载](docs/book/00-Introduction.md#源码下载) - * [编码样式](docs/book/00-Introduction.md#编码样式) - * [BUG提交](docs/book/00-Introduction.md#BUG提交) - * [邮箱订阅](docs/book/00-Introduction.md#邮箱订阅) - * [Java图形界面](docs/book/00-Introduction.md#Java图形界面) - -* [第一章 对象的概念](docs/book/01-What-is-an-Object.md) - * [抽象](docs/book/01-What-is-an-Object.md#抽象) - * [接口](docs/book/01-What-is-an-Object.md#接口) - * [服务提供](docs/book/01-What-is-an-Object.md#服务提供) - * [封装](docs/book/01-What-is-an-Object.md#封装) - * [复用](docs/book/01-What-is-an-Object.md#复用) - * [继承](docs/book/01-What-is-an-Object.md#继承) - * [多态](docs/book/01-What-is-an-Object.md#多态) - * [单继承](docs/book/01-What-is-an-Object.md#单继承) - * [集合](docs/book/01-What-is-an-Object.md#集合) - * [生命周期](docs/book/01-What-is-an-Object.md#生命周期) - * [异常处理](docs/book/01-What-is-an-Object.md#异常处理) - * [本章小结](docs/book/01-What-is-an-Object.md#本章小结) -* [第二章 安装Java和本书用例](docs/book/02-Installing-Java-and-the-Book-Examples.md) - * [编辑器](docs/book/02-Installing-Java-and-the-Book-Examples.md#编辑器) - * [Shell](docs/book/02-Installing-Java-and-the-Book-Examples.md#Shell) - * [Java安装](docs/book/02-Installing-Java-and-the-Book-Examples.md#Java安装) - * [校验安装](docs/book/02-Installing-Java-and-the-Book-Examples.md#校验安装) - * [安装和运行代码示例](docs/book/02-Installing-Java-and-the-Book-Examples.md#安装和运行代码示例) -* [第三章 万物皆对象](docs/book/03-Objects-Everywhere.md) - * [对象操纵](docs/book/03-Objects-Everywhere.md#对象操纵) - * [对象创建](docs/book/03-Objects-Everywhere.md#对象创建) - * [代码注释](docs/book/03-Objects-Everywhere.md#代码注释) - * [对象清理](docs/book/03-Objects-Everywhere.md#对象清理) - * [类的创建](docs/book/03-Objects-Everywhere.md#类的创建) - * [程序编写](docs/book/03-Objects-Everywhere.md#程序编写) - * [小试牛刀](docs/book/03-Objects-Everywhere.md#小试牛刀) - * [编码风格](docs/book/03-Objects-Everywhere.md#编码风格) - * [本章小结](docs/book/03-Objects-Everywhere.md#本章小结) -* [第四章 运算符](docs/book/04-Operators.md) - * [使用说明](docs/book/04-Operators.md#使用说明) - * [优先级](docs/book/04-Operators.md#优先级) - * [赋值](docs/book/04-Operators.md#赋值) - * [算术运算符](docs/book/04-Operators.md#算术运算符) - * [递增和递减](docs/book/04-Operators.md#递增和递减) - * [关系运算符](docs/book/04-Operators.md#关系运算符) - * [逻辑运算符](docs/book/04-Operators.md#逻辑运算符) - * [字面值常量](docs/book/04-Operators.md#字面值常量) - * [按位运算符](docs/book/04-Operators.md#按位运算符) - * [移位运算符](docs/book/04-Operators.md#移位运算符) - * [三元运算符](docs/book/04-Operators.md#三元运算符) - * [字符串运算符](docs/book/04-Operators.md#字符串运算符) - * [常见陷阱](docs/book/04-Operators.md#常见陷阱) - * [类型转换](docs/book/04-Operators.md#类型转换) - * [Java没有sizeof](docs/book/04-Operators.md#Java没有sizeof) - * [运算符总结](docs/book/04-Operators.md#运算符总结) - * [本章小结](docs/book/04-Operators.md#本章小结) -* [第五章 控制流](docs/book/05-Control-Flow.md) - * [true和false](docs/book/05-Control-Flow.md#true和false) - * [if-else](docs/book/05-Control-Flow.md#if-else) - * [迭代语句](docs/book/05-Control-Flow.md#迭代语句) - * [for-in语法](docs/book/05-Control-Flow.md#for-in语法) - * [return](docs/book/05-Control-Flow.md#return) - * [break和continue](docs/book/05-Control-Flow.md#break和continue) - * [臭名昭著的goto](docs/book/05-Control-Flow.md#臭名昭著的goto) - * [switch](docs/book/05-Control-Flow.md#switch) - * [switch字符串](docs/book/05-Control-Flow.md#switch字符串) - * [本章小结](docs/book/05-Control-Flow.md#本章小结) -* [第六章 初始化和清理](docs/book/06-Housekeeping.md) - * [利用构造器保证初始化](docs/book/06-Housekeeping.md#利用构造器保证初始化) - * [方法重载](docs/book/06-Housekeeping.md#方法重载) - * [无参构造器](docs/book/06-Housekeeping.md#无参构造器) - * [this关键字](docs/book/06-Housekeeping.md#this关键字) - * [垃圾回收器](docs/book/06-Housekeeping.md#垃圾回收器) - * [成员初始化](docs/book/06-Housekeeping.md#成员初始化) - * [构造器初始化](docs/book/06-Housekeeping.md#构造器初始化) - * [数组初始化](docs/book/06-Housekeeping.md#数组初始化) - * [枚举类型](docs/book/06-Housekeeping.md#枚举类型) - * [本章小结](docs/book/06-Housekeeping.md#本章小结) -* [第七章 封装](docs/book/07-Implementation-Hiding.md) - * [包的概念](docs/book/07-Implementation-Hiding.md#包的概念) - * [访问权限修饰符](docs/book/07-Implementation-Hiding.md#访问权限修饰符) - * [接口和实现](docs/book/07-Implementation-Hiding.md#接口和实现) - * [类访问权限](docs/book/07-Implementation-Hiding.md#类访问权限) - * [本章小结](docs/book/07-Implementation-Hiding.md#本章小结) -* [第八章 复用](docs/book/08-Reuse.md) - * [组合语法](docs/book/08-Reuse.md#组合语法) - * [继承语法](docs/book/08-Reuse.md#继承语法) - * [委托](docs/book/08-Reuse.md#委托) - * [结合组合与继承](docs/book/08-Reuse.md#结合组合与继承) - * [组合与继承的选择](docs/book/08-Reuse.md#组合与继承的选择) - * [protected](docs/book/08-Reuse.md#protected) - * [向上转型](docs/book/08-Reuse.md#向上转型) - * [final关键字](docs/book/08-Reuse.md#final关键字) - * [类初始化和加载](docs/book/08-Reuse.md#类初始化和加载) - * [本章小结](docs/book/08-Reuse.md#本章小结) -* [第九章 多态](docs/book/09-Polymorphism.md) - * [向上转型回溯](docs/book/09-Polymorphism.md#向上转型回溯) - * [深入理解](docs/book/09-Polymorphism.md#深入理解) - * [构造器和多态](docs/book/09-Polymorphism.md#构造器和多态) - * [返回类型协变](docs/book/09-Polymorphism.md#返回类型协变) - * [使用继承设计](docs/book/09-Polymorphism.md#使用继承设计) - * [本章小结](docs/book/09-Polymorphism.md#本章小结) -* [第十章 接口](docs/book/10-Interfaces.md) - * [抽象类和方法](docs/book/10-Interfaces.md#抽象类和方法) - * [接口创建](docs/book/10-Interfaces.md#接口创建) - * [抽象类和接口](docs/book/10-Interfaces.md#抽象类和接口) - * [完全解耦](docs/book/10-Interfaces.md#完全解耦) - * [多接口结合](docs/book/10-Interfaces.md#多接口结合) - * [使用继承扩展接口](docs/book/10-Interfaces.md#使用继承扩展接口) - * [接口适配](docs/book/10-Interfaces.md#接口适配) - * [接口字段](docs/book/10-Interfaces.md#接口字段) - * [接口嵌套](docs/book/10-Interfaces.md#接口嵌套) - * [接口和工厂方法模式](docs/book/10-Interfaces.md#接口和工厂方法模式) - * [本章小结](docs/book/10-Interfaces.md#本章小结) -* [第十一章 内部类](docs/book/11-Inner-Classes.md) - * [创建内部类](docs/book/11-Inner-Classes.md#创建内部类) - * [链接外部类](docs/book/11-Inner-Classes.md#链接外部类) - * [内部类this和new的使用](docs/book/11-Inner-Classes.md#内部类this和new的使用) - * [内部类向上转型](docs/book/11-Inner-Classes.md#内部类向上转型) - * [内部类方法和作用域](docs/book/11-Inner-Classes.md#内部类方法和作用域) - * [匿名内部类](docs/book/11-Inner-Classes.md#匿名内部类) - * [嵌套类](docs/book/11-Inner-Classes.md#嵌套类) - * [为什么需要内部类](docs/book/11-Inner-Classes.md#为什么需要内部类) - * [继承内部类](docs/book/11-Inner-Classes.md#继承内部类) - * [重写内部类](docs/book/11-Inner-Classes.md#重写内部类) - * [内部类局部变量](docs/book/11-Inner-Classes.md#内部类局部变量) - * [内部类标识符](docs/book/11-Inner-Classes.md#内部类标识符) - * [本章小结](docs/book/11-Inner-Classes.md#本章小结) -* [第十二章 集合](docs/book/12-Collections.md) - * [泛型和类型安全的集合](docs/book/12-Collections.md#泛型和类型安全的集合) - * [基本概念](docs/book/12-Collections.md#基本概念) - * [添加元素组](docs/book/12-Collections.md#添加元素组) - * [集合的打印](docs/book/12-Collections.md#集合的打印) - * [列表List](docs/book/12-Collections.md#列表List) - * [迭代器Iterators](docs/book/12-Collections.md#迭代器Iterators) - * [链表LinkedList](docs/book/12-Collections.md#链表LinkedList) - * [堆栈Stack](docs/book/12-Collections.md#堆栈Stack) - * [集合Set](docs/book/12-Collections.md#集合Set) - * [映射Map](docs/book/12-Collections.md#映射Map) - * [队列Queue](docs/book/12-Collections.md#队列Queue) - * [集合与迭代器](docs/book/12-Collections.md#集合与迭代器) - * [for-in和迭代器](docs/book/12-Collections.md#for-in和迭代器) - * [本章小结](docs/book/12-Collections.md#本章小结) -* [第十三章 函数式编程](docs/book/13-Functional-Programming.md) - * [新旧对比](docs/book/13-Functional-Programming.md#新旧对比) - * [Lambda表达式](docs/book/13-Functional-Programming.md#Lambda表达式) - * [方法引用](docs/book/13-Functional-Programming.md#方法引用) - * [函数式接口](docs/book/13-Functional-Programming.md#函数式接口) - * [高阶函数](docs/book/13-Functional-Programming.md#高阶函数) - * [闭包](docs/book/13-Functional-Programming.md#闭包) - * [函数组合](docs/book/13-Functional-Programming.md#函数组合) - * [柯里化和部分求值](docs/book/13-Functional-Programming.md#柯里化和部分求值) - * [纯函数式编程](docs/book/13-Functional-Programming.md#纯函数式编程) - * [本章小结](docs/book/13-Functional-Programming.md#本章小结) -* [第十四章 流式编程](docs/book/14-Streams.md) - * [流支持](docs/book/14-Streams.md#流支持) - * [流创建](docs/book/14-Streams.md#流创建) - * [中级流操作](docs/book/14-Streams.md#中级流操作) - * [Optional类](docs/book/14-Streams.md#Optional类) - * [终端操作](docs/book/14-Streams.md#终端操作) - * [本章小结](docs/book/14-Streams.md#本章小结) -* [第十五章 异常](docs/book/15-Exceptions.md) - * [异常概念](docs/book/15-Exceptions.md#异常概念) - * [基本异常](docs/book/15-Exceptions.md#基本异常) - * [异常捕获](docs/book/15-Exceptions.md#异常捕获) - * [自定义异常](docs/book/15-Exceptions.md#自定义异常) - * [异常规范](docs/book/15-Exceptions.md#异常规范) - * [任意异常捕获](docs/book/15-Exceptions.md#任意异常捕获) - * [Java标准异常](docs/book/15-Exceptions.md#Java标准异常) - * [finally关键字](docs/book/15-Exceptions.md#finally关键字) - * [异常限制](docs/book/15-Exceptions.md#异常限制) - * [异常构造](docs/book/15-Exceptions.md#异常构造) - * [Try-With-Resources用法](docs/book/15-Exceptions.md#Try-With-Resources用法) - * [异常匹配](docs/book/15-Exceptions.md#异常匹配) - * [异常准则](docs/book/15-Exceptions.md#异常准则) - * [异常指南](docs/book/15-Exceptions.md#异常指南) - * [本章小结](docs/book/15-Exceptions.md#本章小结) -* [第十六章 代码校验](docs/book/16-Validating-Your-Code.md) - * [测试](docs/book/16-Validating-Your-Code.md#测试) - * [前提条件](docs/book/16-Validating-Your-Code.md#前提条件) - * [测试驱动开发](docs/book/16-Validating-Your-Code.md#测试驱动开发) - * [日志](docs/book/16-Validating-Your-Code.md#日志) - * [调试](docs/book/16-Validating-Your-Code.md#调试) - * [基准测试](docs/book/16-Validating-Your-Code.md#基准测试) - * [分析和优化](docs/book/16-Validating-Your-Code.md#分析和优化) - * [风格检测](docs/book/16-Validating-Your-Code.md#风格检测) - * [静态错误分析](docs/book/16-Validating-Your-Code.md#静态错误分析) - * [代码重审](docs/book/16-Validating-Your-Code.md#代码重审) - * [结对编程](docs/book/16-Validating-Your-Code.md#结对编程) - * [重构](docs/book/16-Validating-Your-Code.md#重构) - * [持续集成](docs/book/16-Validating-Your-Code.md#持续集成) - * [本章小结](docs/book/16-Validating-Your-Code.md#本章小结) -* [第十七章 文件](docs/book/17-Files.md) - * [文件和目录路径](docs/book/17-Files.md#文件和目录路径) - * [目录](docs/book/17-Files.md#目录) - * [文件系统](docs/book/17-Files.md#文件系统) - * [路径监听](docs/book/17-Files.md#路径监听) - * [文件查找](docs/book/17-Files.md#文件查找) - * [文件读写](docs/book/17-Files.md#文件读写) - * [本章小结](docs/book/17-Files.md#本章小结) -* [第十八章 字符串](docs/book/18-Strings.md) - * [字符串的不可变](docs/book/18-Strings.md#字符串的不可变) - * [重载和StringBuilder](docs/book/18-Strings.md#重载和StringBuilder) - * [意外递归](docs/book/18-Strings.md#意外递归) - * [字符串操作](docs/book/18-Strings.md#字符串操作) - * [格式化输出](docs/book/18-Strings.md#格式化输出) - * [常规表达式](docs/book/18-Strings.md#常规表达式) - * [扫描输入](docs/book/18-Strings.md#扫描输入) - * [StringTokenizer类](docs/book/18-Strings.md#StringTokenizer类) - * [本章小结](docs/book/18-Strings.md#本章小结) -* [第十九章 类型信息](docs/book/19-Type-Information.md) - * [运行时类型信息](docs/book/19-Type-Information.md#运行时类型信息) - * [类的对象](docs/book/19-Type-Information.md#类的对象) - * [类型转换检测](docs/book/19-Type-Information.md#类型转换检测) - * [注册工厂](docs/book/19-Type-Information.md#注册工厂) - * [类的等价比较](docs/book/19-Type-Information.md#类的等价比较) - * [反射运行时类信息](docs/book/19-Type-Information.md#反射运行时类信息) - * [动态代理](docs/book/19-Type-Information.md#动态代理) - * [Optional类](docs/book/19-Type-Information.md#Optional类) - * [接口和类型](docs/book/19-Type-Information.md#接口和类型) - * [本章小结](docs/book/19-Type-Information.md#本章小结) -* [第二十章 泛型](docs/book/20-Generics.md) - * [简单泛型](docs/book/20-Generics.md#简单泛型) - * [泛型接口](docs/book/20-Generics.md#泛型接口) - * [泛型方法](docs/book/20-Generics.md#泛型方法) - * [复杂模型构建](docs/book/20-Generics.md#复杂模型构建) - * [泛型擦除](docs/book/20-Generics.md#泛型擦除) - * [补偿擦除](docs/book/20-Generics.md#补偿擦除) - * [边界](docs/book/20-Generics.md#边界) - * [通配符](docs/book/20-Generics.md#通配符) - * [问题](docs/book/20-Generics.md#问题) - * [自我约束类型](docs/book/20-Generics.md#自我约束类型) - * [动态类型安全](docs/book/20-Generics.md#动态类型安全) - * [泛型异常](docs/book/20-Generics.md#泛型异常) - * [混入](docs/book/20-Generics.md#混入) - * [潜在类型](docs/book/20-Generics.md#潜在类型) - * [补偿不足](docs/book/20-Generics.md#补偿不足) - * [辅助潜在类型](docs/book/20-Generics.md#辅助潜在类型) - * [泛型的优劣](docs/book/20-Generics.md#泛型的优劣) -* [第二十一章 数组](docs/book/21-Arrays.md) - * [数组特性](docs/book/21-Arrays.md#数组特性) - * [一等对象](docs/book/21-Arrays.md#一等对象) - * [返回数组](docs/book/21-Arrays.md#返回数组) - * [多维数组](docs/book/21-Arrays.md#多维数组) - * [泛型数组](docs/book/21-Arrays.md#泛型数组) - * [Arrays的fill方法](docs/book/21-Arrays.md#Arrays的fill方法) - * [Arrays的setAll方法](docs/book/21-Arrays.md#Arrays的setAll方法) - * [增量生成](docs/book/21-Arrays.md#增量生成) - * [随机生成](docs/book/21-Arrays.md#随机生成) - * [泛型和基本数组](docs/book/21-Arrays.md#泛型和基本数组) - * [数组元素修改](docs/book/21-Arrays.md#数组元素修改) - * [数组并行](docs/book/21-Arrays.md#数组并行) - * [Arrays工具类](docs/book/21-Arrays.md#Arrays工具类) - * [数组拷贝](docs/book/21-Arrays.md#数组拷贝) - * [数组比较](docs/book/21-Arrays.md#数组比较) - * [流和数组](docs/book/21-Arrays.md#流和数组) - * [数组排序](docs/book/21-Arrays.md#数组排序) - * [binarySearch二分查找](docs/book/21-Arrays.md#binarySearch二分查找) - * [parallelPrefix并行前缀](docs/book/21-Arrays.md#parallelPrefix并行前缀) - * [本章小结](docs/book/21-Arrays.md#本章小结) -* [第二十二章 枚举](docs/book/22-Enumerations.md) - * [基本功能](docs/book/22-Enumerations.md#基本功能) - * [方法添加](docs/book/22-Enumerations.md#方法添加) - * [switch语句](docs/book/22-Enumerations.md#switch语句) - * [values方法](docs/book/22-Enumerations.md#values方法) - * [实现而非继承](docs/book/22-Enumerations.md#实现而非继承) - * [随机选择](docs/book/22-Enumerations.md#随机选择) - * [使用接口组织](docs/book/22-Enumerations.md#使用接口组织) - * [使用EnumSet替代Flags](docs/book/22-Enumerations.md#使用EnumSet替代Flags) - * [使用EnumMap](docs/book/22-Enumerations.md#使用EnumMap) - * [常量特定方法](docs/book/22-Enumerations.md#常量特定方法) - * [多次调度](docs/book/22-Enumerations.md#多次调度) - * [本章小结](docs/book/22-Enumerations.md#本章小结) -* [第二十三章 注解](docs/book/23-Annotations.md) - * [基本语法](docs/book/23-Annotations.md#基本语法) - * [编写注解处理器](docs/book/23-Annotations.md#编写注解处理器) - * [使用javac处理注解](docs/book/23-Annotations.md#使用javac处理注解) - * [基于注解的单元测试](docs/book/23-Annotations.md#基于注解的单元测试) - * [本章小结](docs/book/23-Annotations.md#本章小结) -* [第二十四章 并发编程](docs/book/24-Concurrent-Programming.md) - * [术语问题](docs/book/24-Concurrent-Programming.md#术语问题) - * [并发的超能力](docs/book/24-Concurrent-Programming.md#并发的超能力) - * [针对速度](docs/book/24-Concurrent-Programming.md#针对速度) - * [四句格言](docs/book/24-Concurrent-Programming.md#四句格言) - * [残酷的真相](docs/book/24-Concurrent-Programming.md#残酷的真相) - * [本章其余部分](docs/book/24-Concurrent-Programming.md#本章其余部分) - * [并行流](docs/book/24-Concurrent-Programming.md#并行流) - * [创建和运行任务](docs/book/24-Concurrent-Programming.md#创建和运行任务) - * [终止耗时任务](docs/book/24-Concurrent-Programming.md#终止耗时任务) - * [CompletableFuture类](docs/book/24-Concurrent-Programming.md#CompletableFuture类) - * [死锁](docs/book/24-Concurrent-Programming.md#死锁) - * [构造函数非线程安全](docs/book/24-Concurrent-Programming.md#构造函数非线程安全) - * [复杂性和代价](docs/book/24-Concurrent-Programming.md#复杂性和代价) - * [本章小结](docs/book/24-Concurrent-Programming.md#本章小结) -* [第二十五章 设计模式](docs/book/25-Patterns.md) - * [概念](docs/book/25-Patterns.md#概念) - * [构建型](docs/book/25-Patterns.md#构建型) - * [面向实施](docs/book/25-Patterns.md#面向实施) - * [工厂模式](docs/book/25-Patterns.md#工厂模式) - * [函数对象](docs/book/25-Patterns.md#函数对象) - * [接口改变](docs/book/25-Patterns.md#接口改变) - * [解释器](docs/book/25-Patterns.md#解释器) - * [回调](docs/book/25-Patterns.md#回调) - * [多次调度](docs/book/25-Patterns.md#多次调度) - * [模式重构](docs/book/25-Patterns.md#模式重构) - * [抽象用法](docs/book/25-Patterns.md#抽象用法) - * [多次派遣](docs/book/25-Patterns.md#多次派遣) - * [访问者模式](docs/book/25-Patterns.md#访问者模式) - * [RTTI的优劣](docs/book/25-Patterns.md#RTTI的优劣) - * [本章小结](docs/book/25-Patterns.md#本章小结) - -* [附录:补充](docs/book/Appendix-Supplements.md) - * [可下载的补充](docs/book/Appendix-Supplements.md#可下载的补充) - * [通过Thinking-in-C来巩固Java基础](docs/book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础) - * [动手实践](docs/book/Appendix-Supplements.md#动手实践) -* [附录:编程指南](docs/book/Appendix-Programming-Guidelines.md) - * [设计](docs/book/Appendix-Programming-Guidelines.md#设计) - * [实现](docs/book/Appendix-Programming-Guidelines.md#实现) -* [附录:文档注释](docs/book/Appendix-Javadoc.md) -* [附录:对象传递和返回](docs/book/Appendix-Passing-and-Returning-Objects.md) - * [传递引用](docs/book/Appendix-Passing-and-Returning-Objects.md#传递引用) - * [本地拷贝](docs/book/Appendix-Passing-and-Returning-Objects.md#本地拷贝) - * [控制克隆](docs/book/Appendix-Passing-and-Returning-Objects.md#控制克隆) - * [不可变类](docs/book/Appendix-Passing-and-Returning-Objects.md#不可变类) - * [本章小结](docs/book/Appendix-Passing-and-Returning-Objects.md#本章小结) -* [附录:流式IO](docs/book/Appendix-IO-Streams.md) - * [输入流类型](docs/book/Appendix-IO-Streams.md#输入流类型) - * [输出流类型](docs/book/Appendix-IO-Streams.md#输出流类型) - * [添加属性和有用的接口](docs/book/Appendix-IO-Streams.md#添加属性和有用的接口) - * [Reader和Writer](docs/book/Appendix-IO-Streams.md#Reader和Writer) - * [RandomAccessFile类](docs/book/Appendix-IO-Streams.md#RandomAccessFile类) - * [IO流典型用途](docs/book/Appendix-IO-Streams.md#IO流典型用途) - * [本章小结](docs/book/Appendix-IO-Streams.md#本章小结) -* [附录:标准IO](docs/book/Appendix-Standard-IO.md) - * [执行控制](docs/book/Appendix-Standard-IO.md#执行控制) -* [附录:新IO](docs/book/Appendix-New-IO.md) - * [ByteBuffer](docs/book/Appendix-New-IO.md#ByteBuffer) - * [转换数据](docs/book/Appendix-New-IO.md#数据转换) - * [获取原始类型](docs/book/Appendix-New-IO.md#基本类型获取) - * [视图缓冲区](docs/book/Appendix-New-IO.md#视图缓冲区) - * [使用缓冲区进行数据操作](docs/book/Appendix-New-IO.md#缓冲区数据操作) - * [内存映射文件](docs/book/Appendix-New-IO.md#内存映射文件) - * [文件锁定](docs/book/Appendix-New-IO.md#文件锁定) -* [附录:理解equals和hashCode方法](docs/book/Appendix-Understanding-equals-and-hashCode.md) - * [equals典范](docs/book/Appendix-Understanding-equals-and-hashCode.md#equals典范) - * [哈希和哈希码](docs/book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码) - * [调整HashMap](docs/book/Appendix-Understanding-equals-and-hashCode.md#调整HashMap) -* [附录:集合主题](docs/book/Appendix-Collection-Topics.md) - * [示例数据](docs/book/Appendix-Collection-Topics.md#示例数据) - * [List行为](docs/book/Appendix-Collection-Topics.md#List行为) - * [Set行为](docs/book/Appendix-Collection-Topics.md#Set行为) - * [在Map中使用函数式操作](docs/book/Appendix-Collection-Topics.md#在Map中使用函数式操作) - * [选择Map片段](docs/book/Appendix-Collection-Topics.md#选择Map片段) - * [填充集合](docs/book/Appendix-Collection-Topics.md#填充集合) - * [使用享元(Flyweight)自定义Collection和Map](docs/book/Appendix-Collection-Topics.md#使用享元(Flyweight)自定义Collection和Map) - * [集合功能](docs/book/Appendix-Collection-Topics.md#集合功能) - * [可选操作](docs/book/Appendix-Collection-Topics.md#可选操作) - * [Set和存储顺序](docs/book/Appendix-Collection-Topics.md#Set和存储顺序) - * [队列](docs/book/Appendix-Collection-Topics.md#队列) - * [理解Map](docs/book/Appendix-Collection-Topics.md#理解Map) - * [集合工具类](docs/book/Appendix-Collection-Topics.md#集合工具类) - * [持有引用](docs/book/Appendix-Collection-Topics.md#持有引用) - * [Java 1.0 / 1.1 的集合类](docs/book/Appendix-Collection-Topics.md#避免旧式类库) - * [本章小结](docs/book/Appendix-Collection-Topics.md#本章小结) -* [附录:并发底层原理](docs/book/Appendix-Low-Level-Concurrency.md) - * [线程](docs/book/Appendix-Low-Level-Concurrency.md#线程) - * [异常捕获](docs/book/Appendix-Low-Level-Concurrency.md#异常捕获) - * [资源共享](docs/book/Appendix-Low-Level-Concurrency.md#资源共享) - * [volatile关键字](docs/book/Appendix-Low-Level-Concurrency.md#volatile关键字) - * [原子性](docs/book/Appendix-Low-Level-Concurrency.md#原子性) - * [临界区](docs/book/Appendix-Low-Level-Concurrency.md#临界区) - * [库组件](docs/book/Appendix-Low-Level-Concurrency.md#库组件) - * [本章小结](docs/book/Appendix-Low-Level-Concurrency.md#本章小结) -* [附录:数据压缩](docs/book/Appendix-Data-Compression.md) - * [使用Gzip简单压缩](docs/book/Appendix-Data-Compression.md#使用Gzip简单压缩) - * [使用zip多文件存储](docs/book/Appendix-Data-Compression.md#使用zip多文件存储) - * [Java的jar](docs/book/Appendix-Data-Compression.md#Java的jar) -* [附录:对象序列化](docs/book/Appendix-Object-Serialization.md) - * [查找类](docs/book/Appendix-Object-Serialization.md#查找类) - * [控制序列化](docs/book/Appendix-Object-Serialization.md#控制序列化) - * [使用持久化](docs/book/Appendix-Object-Serialization.md#使用持久化) -* [附录:静态语言类型检查](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) - * [前言](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言) - * [静态类型检查和测试](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试) - * [如何提升打字](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字) - * [生产力的成本](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本) - * [静态和动态](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态) -* [附录:C++和Java的优良传统](docs/book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) -* [附录:成为一名程序员](docs/book/Appendix-Becoming-a-Programmer.md) - * [如何开始](docs/book/Appendix-Becoming-a-Programmer.md#如何开始) - * [码农生涯](docs/book/Appendix-Becoming-a-Programmer.md#码农生涯) - * [百分之五的神话](docs/book/Appendix-Becoming-a-Programmer.md#百分之五的神话) - * [重在动手](docs/book/Appendix-Becoming-a-Programmer.md#重在动手) - * [像打字般编程](docs/book/Appendix-Becoming-a-Programmer.md#像打字般编程) - * [做你喜欢的事](docs/book/Appendix-Becoming-a-Programmer.md#做你喜欢的事) -* [词汇表](docs/book/GLOSSARY.md) + +### [译者的话](README.md) + +### [封面](book/00-On-Java-8.md) + +### [前言](book/00-Preface.md) + +### [简介](book/00-Introduction.md) + +### [第一章 对象的概念](book/01-What-is-an-Object.md) + * [抽象](book/01-What-is-an-Object.md#抽象) + * [接口](book/01-What-is-an-Object.md#接口) + * [服务提供](book/01-What-is-an-Object.md#服务提供) + * [封装](book/01-What-is-an-Object.md#封装) + * [复用](book/01-What-is-an-Object.md#复用) + * [继承](book/01-What-is-an-Object.md#继承) + * [多态](book/01-What-is-an-Object.md#多态) + * [单继承结构](book/01-What-is-an-Object.md#单继承结构) + * [集合](book/01-What-is-an-Object.md#集合) + * [对象创建与生命周期](book/01-What-is-an-Object.md#对象创建与生命周期) + * [异常处理](book/01-What-is-an-Object.md#异常处理) + * [本章小结](book/01-What-is-an-Object.md#本章小结) + +### [第二章 安装Java和本书用例](book/02-Installing-Java-and-the-Book-Examples.md) + * [编辑器](book/02-Installing-Java-and-the-Book-Examples.md#编辑器) + * [Shell](book/02-Installing-Java-and-the-Book-Examples.md#Shell) + * [Java安装](book/02-Installing-Java-and-the-Book-Examples.md#Java安装) + * [校验安装](book/02-Installing-Java-and-the-Book-Examples.md#校验安装) + * [安装和运行代码示例](book/02-Installing-Java-and-the-Book-Examples.md#安装和运行代码示例) + +### [第三章 万物皆对象](book/03-Objects-Everywhere.md) + * [对象操纵](book/03-Objects-Everywhere.md#对象操纵) + * [对象创建](book/03-Objects-Everywhere.md#对象创建) + * [代码注释](book/03-Objects-Everywhere.md#代码注释) + * [对象清理](book/03-Objects-Everywhere.md#对象清理) + * [类的创建](book/03-Objects-Everywhere.md#类的创建) + * [程序编写](book/03-Objects-Everywhere.md#程序编写) + * [小试牛刀](book/03-Objects-Everywhere.md#小试牛刀) + * [编码风格](book/03-Objects-Everywhere.md#编码风格) + * [本章小结](book/03-Objects-Everywhere.md#本章小结) + +### [第四章 运算符](book/04-Operators.md) + * [开始使用](book/04-Operators.md#开始使用) + * [优先级](book/04-Operators.md#优先级) + * [赋值](book/04-Operators.md#赋值) + * [算术运算符](book/04-Operators.md#算术运算符) + * [递增和递减](book/04-Operators.md#递增和递减) + * [关系运算符](book/04-Operators.md#关系运算符) + * [逻辑运算符](book/04-Operators.md#逻辑运算符) + * [字面值常量](book/04-Operators.md#字面值常量) + * [位运算符](book/04-Operators.md#位运算符) + * [移位运算符](book/04-Operators.md#移位运算符) + * [三元运算符](book/04-Operators.md#三元运算符) + * [字符串运算符](book/04-Operators.md#字符串运算符) + * [常见陷阱](book/04-Operators.md#常见陷阱) + * [类型转换](book/04-Operators.md#类型转换) + * [Java没有sizeof](book/04-Operators.md#Java没有sizeof) + * [运算符总结](book/04-Operators.md#运算符总结) + * [本章小结](book/04-Operators.md#本章小结) + +### [第五章 控制流](book/05-Control-Flow.md) + * [true和false](book/05-Control-Flow.md#true和false) + * [if-else](book/05-Control-Flow.md#if-else) + * [迭代语句](book/05-Control-Flow.md#迭代语句) + * [for-in 语法](book/05-Control-Flow.md#for-in-语法) + * [return](book/05-Control-Flow.md#return) + * [break 和 continue](book/05-Control-Flow.md#break-和-continue) + * [臭名昭著的 goto](book/05-Control-Flow.md#臭名昭著的-goto) + * [switch](book/05-Control-Flow.md#switch) + * [switch 字符串](book/05-Control-Flow.md#switch-字符串) + * [本章小结](book/05-Control-Flow.md#本章小结) + +### [第六章 初始化和清理](book/06-Housekeeping.md) + * [利用构造器保证初始化](book/06-Housekeeping.md#利用构造器保证初始化) + * [方法重载](book/06-Housekeeping.md#方法重载) + * [无参构造器](book/06-Housekeeping.md#无参构造器) + * [this关键字](book/06-Housekeeping.md#this关键字) + * [垃圾回收器](book/06-Housekeeping.md#垃圾回收器) + * [成员初始化](book/06-Housekeeping.md#成员初始化) + * [构造器初始化](book/06-Housekeeping.md#构造器初始化) + * [数组初始化](book/06-Housekeeping.md#数组初始化) + * [枚举类型](book/06-Housekeeping.md#枚举类型) + * [本章小结](book/06-Housekeeping.md#本章小结) + +### [第七章 封装](book/07-Implementation-Hiding.md) + * [包的概念](book/07-Implementation-Hiding.md#包的概念) + * [访问权限修饰符](book/07-Implementation-Hiding.md#访问权限修饰符) + * [接口和实现](book/07-Implementation-Hiding.md#接口和实现) + * [类访问权限](book/07-Implementation-Hiding.md#类访问权限) + * [本章小结](book/07-Implementation-Hiding.md#本章小结) + +### [第八章 复用](book/08-Reuse.md) + * [组合语法](book/08-Reuse.md#组合语法) + * [继承语法](book/08-Reuse.md#继承语法) + * [委托](book/08-Reuse.md#委托) + * [结合组合与继承](book/08-Reuse.md#结合组合与继承) + * [组合与继承的选择](book/08-Reuse.md#组合与继承的选择) + * [protected](book/08-Reuse.md#protected) + * [向上转型](book/08-Reuse.md#向上转型) + * [final关键字](book/08-Reuse.md#final关键字) + * [类初始化和加载](book/08-Reuse.md#类初始化和加载) + * [本章小结](book/08-Reuse.md#本章小结) + +### [第九章 多态](book/09-Polymorphism.md) + * [向上转型回顾](book/09-Polymorphism.md#向上转型回顾) + * [转机](book/09-Polymorphism.md#转机) + * [构造器和多态](book/09-Polymorphism.md#构造器和多态) + * [协变返回类型](book/09-Polymorphism.md#协变返回类型) + * [使用继承设计](book/09-Polymorphism.md#使用继承设计) + * [本章小结](book/09-Polymorphism.md#本章小结) + +### [第十章 接口](book/10-Interfaces.md) + * [抽象类和方法](book/10-Interfaces.md#抽象类和方法) + * [接口创建](book/10-Interfaces.md#接口创建) + * [抽象类和接口](book/10-Interfaces.md#抽象类和接口) + * [完全解耦](book/10-Interfaces.md#完全解耦) + * [多接口结合](book/10-Interfaces.md#多接口结合) + * [使用继承扩展接口](book/10-Interfaces.md#使用继承扩展接口) + * [接口适配](book/10-Interfaces.md#接口适配) + * [接口字段](book/10-Interfaces.md#接口字段) + * [接口嵌套](book/10-Interfaces.md#接口嵌套) + * [接口和工厂方法模式](book/10-Interfaces.md#接口和工厂方法模式) + * [本章小结](book/10-Interfaces.md#本章小结) + +### [第十一章 内部类](book/11-Inner-Classes.md) + * [创建内部类](book/11-Inner-Classes.md#创建内部类) + * [链接外部类](book/11-Inner-Classes.md#链接外部类) + * [使用 .this 和 .new](book/11-Inner-Classes.md#使用-this-和-new) + * [内部类与向上转型](book/11-Inner-Classes.md#内部类与向上转型) + * [内部类方法和作用域](book/11-Inner-Classes.md#内部类方法和作用域) + * [匿名内部类](book/11-Inner-Classes.md#匿名内部类) + * [嵌套类](book/11-Inner-Classes.md#嵌套类) + * [为什么需要内部类](book/11-Inner-Classes.md#为什么需要内部类) + * [继承内部类](book/11-Inner-Classes.md#继承内部类) + * [内部类可以被覆盖么?](book/11-Inner-Classes.md#内部类可以被覆盖么?) + * [局部内部类](book/11-Inner-Classes.md#局部内部类) + * [内部类标识符](book/11-Inner-Classes.md#内部类标识符) + * [本章小结](book/11-Inner-Classes.md#本章小结) + +### [第十二章 集合](book/12-Collections.md) + * [泛型和类型安全的集合](book/12-Collections.md#泛型和类型安全的集合) + * [基本概念](book/12-Collections.md#基本概念) + * [添加元素组](book/12-Collections.md#添加元素组) + * [集合的打印](book/12-Collections.md#集合的打印) + * [列表List](book/12-Collections.md#列表List) + * [迭代器Iterators](book/12-Collections.md#迭代器Iterators) + * [链表LinkedList](book/12-Collections.md#链表LinkedList) + * [堆栈Stack](book/12-Collections.md#堆栈Stack) + * [集合Set](book/12-Collections.md#集合Set) + * [映射Map](book/12-Collections.md#映射Map) + * [队列Queue](book/12-Collections.md#队列Queue) + * [集合与迭代器](book/12-Collections.md#集合与迭代器) + * [for-in和迭代器](book/12-Collections.md#for-in和迭代器) + * [本章小结](book/12-Collections.md#本章小结) + +### [第十三章 函数式编程](book/13-Functional-Programming.md) + * [新旧对比](book/13-Functional-Programming.md#新旧对比) + * [Lambda表达式](book/13-Functional-Programming.md#Lambda表达式) + * [方法引用](book/13-Functional-Programming.md#方法引用) + * [函数式接口](book/13-Functional-Programming.md#函数式接口) + * [高阶函数](book/13-Functional-Programming.md#高阶函数) + * [闭包](book/13-Functional-Programming.md#闭包) + * [函数组合](book/13-Functional-Programming.md#函数组合) + * [柯里化和部分求值](book/13-Functional-Programming.md#柯里化和部分求值) + * [纯函数式编程](book/13-Functional-Programming.md#纯函数式编程) + * [本章小结](book/13-Functional-Programming.md#本章小结) + +### [第十四章 流式编程](book/14-Streams.md) + * [流支持](book/14-Streams.md#流支持) + * [流创建](book/14-Streams.md#流创建) + * [中间操作](book/14-Streams.md#中间操作) + * [Optional类](book/14-Streams.md#Optional类) + * [终端操作](book/14-Streams.md#终端操作) + * [本章小结](book/14-Streams.md#本章小结) + +### [第十五章 异常](book/15-Exceptions.md) + * [异常概念](book/15-Exceptions.md#异常概念) + * [基本异常](book/15-Exceptions.md#基本异常) + * [异常捕获](book/15-Exceptions.md#异常捕获) + * [自定义异常](book/15-Exceptions.md#自定义异常) + * [异常声明](book/15-Exceptions.md#异常声明) + * [捕获所有异常](book/15-Exceptions.md#捕获所有异常) + * [Java 标准异常](book/15-Exceptions.md#Java-标准异常) + * [使用 finally 进行清理](book/15-Exceptions.md#使用-finally-进行清理) + * [异常限制](book/15-Exceptions.md#异常限制) + * [构造器](book/15-Exceptions.md#构造器) + * [Try-With-Resources 用法](book/15-Exceptions.md#Try-With-Resources-用法) + * [异常匹配](book/15-Exceptions.md#异常匹配) + * [其他可选方式](book/15-Exceptions.md#其他可选方式) + * [异常指南](book/15-Exceptions.md#异常指南) + * [本章小结](book/15-Exceptions.md#本章小结) + * [后记:Exception Bizarro World](book/15-Exceptions.md#后记:Exception-Bizarro-World) + +### [第十六章 代码校验](book/16-Validating-Your-Code.md) + * [测试](book/16-Validating-Your-Code.md#测试) + * [前置条件](book/16-Validating-Your-Code.md#前置条件) + * [测试驱动开发](book/16-Validating-Your-Code.md#测试驱动开发) + * [日志](book/16-Validating-Your-Code.md#日志) + * [调试](book/16-Validating-Your-Code.md#调试) + * [基准测试](book/16-Validating-Your-Code.md#基准测试) + * [剖析和优化](book/16-Validating-Your-Code.md#剖析和优化) + * [风格检测](book/16-Validating-Your-Code.md#风格检测) + * [静态错误分析](book/16-Validating-Your-Code.md#静态错误分析) + * [代码重审](book/16-Validating-Your-Code.md#代码重审) + * [结对编程](book/16-Validating-Your-Code.md#结对编程) + * [重构](book/16-Validating-Your-Code.md#重构) + * [持续集成](book/16-Validating-Your-Code.md#持续集成) + * [本章小结](book/16-Validating-Your-Code.md#本章小结) + +### [第十七章 文件](book/17-Files.md) + * [文件和目录路径](book/17-Files.md#文件和目录路径) + * [目录](book/17-Files.md#目录) + * [文件系统](book/17-Files.md#文件系统) + * [路径监听](book/17-Files.md#路径监听) + * [文件查找](book/17-Files.md#文件查找) + * [文件读写](book/17-Files.md#文件读写) + * [本章小结](book/17-Files.md#本章小结) + +### [第十八章 字符串](book/18-Strings.md) + * [字符串的不可变](book/18-Strings.md#字符串的不可变) + * [+ 的重载与 StringBuilder](book/18-Strings.md#-的重载与-StringBuilder) + * [意外递归](book/18-Strings.md#意外递归) + * [字符串操作](book/18-Strings.md#字符串操作) + * [格式化输出](book/18-Strings.md#格式化输出) + * [正则表达式](book/18-Strings.md#正则表达式) + * [扫描输入](book/18-Strings.md#扫描输入) + * [StringTokenizer类](book/18-Strings.md#StringTokenizer类) + * [本章小结](book/18-Strings.md#本章小结) + +### [第十九章 类型信息](book/19-Type-Information.md) + * [为什么需要 RTTI](book/19-Type-Information.md#为什么需要-RTTI) + * [Class 对象](book/19-Type-Information.md#Class-对象) + * [类型转换检测](book/19-Type-Information.md#类型转换检测) + * [注册工厂](book/19-Type-Information.md#注册工厂) + * [类的等价比较](book/19-Type-Information.md#类的等价比较) + * [反射:运行时类信息](book/19-Type-Information.md#反射:运行时类信息) + * [动态代理](book/19-Type-Information.md#动态代理) + * [Optional类](book/19-Type-Information.md#Optional类) + * [接口和类型](book/19-Type-Information.md#接口和类型) + * [本章小结](book/19-Type-Information.md#本章小结) + +### [第二十章 泛型](book/20-Generics.md) + * [简单泛型](book/20-Generics.md#简单泛型) + * [泛型接口](book/20-Generics.md#泛型接口) + * [泛型方法](book/20-Generics.md#泛型方法) + * [构建复杂模型](book/20-Generics.md#构建复杂模型) + * [泛型擦除](book/20-Generics.md#泛型擦除) + * [补偿擦除](book/20-Generics.md#补偿擦除) + * [边界](book/20-Generics.md#边界) + * [通配符](book/20-Generics.md#通配符) + * [问题](book/20-Generics.md#问题) + * [自限定的类型](book/20-Generics.md#自限定的类型) + * [动态类型安全](book/20-Generics.md#动态类型安全) + * [泛型异常](book/20-Generics.md#泛型异常) + * [混型](book/20-Generics.md#混型) + * [潜在类型机制](book/20-Generics.md#潜在类型机制) + * [对缺乏潜在类型机制的补偿](book/20-Generics.md#对缺乏潜在类型机制的补偿) + * [Java8 中的辅助潜在类型](book/20-Generics.md#Java8-中的辅助潜在类型) + * [总结:类型转换真的如此之糟吗?](book/20-Generics.md#总结:类型转换真的如此之糟吗?) + * [进阶阅读](book/20-Generics.md#进阶阅读) + +### [第二十一章 数组](book/21-Arrays.md) + * [数组特性](book/21-Arrays.md#数组特性) + * [一等对象](book/21-Arrays.md#一等对象) + * [返回数组](book/21-Arrays.md#返回数组) + * [多维数组](book/21-Arrays.md#多维数组) + * [泛型数组](book/21-Arrays.md#泛型数组) + * [Arrays的fill方法](book/21-Arrays.md#Arrays的fill方法) + * [Arrays的setAll方法](book/21-Arrays.md#Arrays的setAll方法) + * [增量生成](book/21-Arrays.md#增量生成) + * [随机生成](book/21-Arrays.md#随机生成) + * [泛型和基本数组](book/21-Arrays.md#泛型和基本数组) + * [数组元素修改](book/21-Arrays.md#数组元素修改) + * [数组并行](book/21-Arrays.md#数组并行) + * [Arrays工具类](book/21-Arrays.md#Arrays工具类) + * [数组拷贝](book/21-Arrays.md#数组拷贝) + * [数组比较](book/21-Arrays.md#数组比较) + * [流和数组](book/21-Arrays.md#流和数组) + * [数组排序](book/21-Arrays.md#数组排序) + * [Arrays.sort 的使用](book/21-Arrays.md#Arrayssort-的使用) + * [并行排序](book/21-Arrays.md#并行排序) + * [binarySearch二分查找](book/21-Arrays.md#binarySearch二分查找) + * [parallelPrefix并行前缀](book/21-Arrays.md#parallelPrefix并行前缀) + * [本章小结](book/21-Arrays.md#本章小结) + +### [第二十二章 枚举](book/22-Enumerations.md) + * [基本 enum 特性](book/22-Enumerations.md#基本-enum-特性) + * [方法添加](book/22-Enumerations.md#方法添加) + * [switch 语句中的 enum](book/22-Enumerations.md#switch-语句中的-enum) + * [values 方法的神秘之处](book/22-Enumerations.md#values-方法的神秘之处) + * [实现而非继承](book/22-Enumerations.md#实现而非继承) + * [随机选择](book/22-Enumerations.md#随机选择) + * [使用接口组织枚举](book/22-Enumerations.md#使用接口组织枚举) + * [使用 EnumSet 替代 Flags](book/22-Enumerations.md#使用-EnumSet-替代-Flags) + * [使用 EnumMap](book/22-Enumerations.md#使用-EnumMap) + * [常量特定方法](book/22-Enumerations.md#常量特定方法) + * [多路分发](book/22-Enumerations.md#多路分发) + * [本章小结](book/22-Enumerations.md#本章小结) + +### [第二十三章 注解](book/23-Annotations.md) + * [基本语法](book/23-Annotations.md#基本语法) + * [编写注解处理器](book/23-Annotations.md#编写注解处理器) + * [使用javac处理注解](book/23-Annotations.md#使用javac处理注解) + * [基于注解的单元测试](book/23-Annotations.md#基于注解的单元测试) + * [本章小结](book/23-Annotations.md#本章小结) + +### [第二十四章 并发编程](book/24-Concurrent-Programming.md) + * [术语问题](book/24-Concurrent-Programming.md#术语问题) + * [并发的超能力](book/24-Concurrent-Programming.md#并发的超能力) + * [并发为速度而生](book/24-Concurrent-Programming.md#并发为速度而生) + * [四句格言](book/24-Concurrent-Programming.md#四句格言) + * [残酷的真相](book/24-Concurrent-Programming.md#残酷的真相) + * [本章其余部分](book/24-Concurrent-Programming.md#本章其余部分) + * [并行流](book/24-Concurrent-Programming.md#并行流) + * [创建和运行任务](book/24-Concurrent-Programming.md#创建和运行任务) + * [终止耗时任务](book/24-Concurrent-Programming.md#终止耗时任务) + * [CompletableFuture 类](book/24-Concurrent-Programming.md#CompletableFuture-类) + * [死锁](book/24-Concurrent-Programming.md#死锁) + * [构造方法非线程安全](book/24-Concurrent-Programming.md#构造方法非线程安全) + * [复杂性和代价](book/24-Concurrent-Programming.md#复杂性和代价) + * [本章小结](book/24-Concurrent-Programming.md#本章小结) + +### [第二十五章 设计模式](book/25-Patterns.md) + * [概念](book/25-Patterns.md#概念) + * [构建应用程序框架](book/25-Patterns.md#构建应用程序框架) + * [面向实现](book/25-Patterns.md#面向实现) + * [工厂模式](book/25-Patterns.md#工厂模式) + * [函数对象](book/25-Patterns.md#函数对象) + * [改变接口](book/25-Patterns.md#改变接口) + * [解释器:运行时的弹性](book/25-Patterns.md#解释器:运行时的弹性) + * [回调](book/25-Patterns.md#回调) + * [多次调度](book/25-Patterns.md#多次调度) + * [模式重构](book/25-Patterns.md#模式重构) + * [抽象用法](book/25-Patterns.md#抽象用法) + * [多次派遣](book/25-Patterns.md#多次派遣) + * [访问者模式](book/25-Patterns.md#访问者模式) + * [RTTI的优劣](book/25-Patterns.md#RTTI的优劣) + * [本章小结](book/25-Patterns.md#本章小结) + +### [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md) + * [如何开始](book/Appendix-Becoming-a-Programmer.md#如何开始) + * [码农生涯](book/Appendix-Becoming-a-Programmer.md#码农生涯) + * [百分之五的神话](book/Appendix-Becoming-a-Programmer.md#百分之五的神话) + * [重在动手](book/Appendix-Becoming-a-Programmer.md#重在动手) + * [像打字般编程](book/Appendix-Becoming-a-Programmer.md#像打字般编程) + * [做你喜欢的事](book/Appendix-Becoming-a-Programmer.md#做你喜欢的事) + +### [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) + * [前言](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言) + * [静态类型检查和测试](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试) + * [如何提升打字](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字) + * [生产力的成本](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本) + * [静态和动态](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态) + +### [附录:集合主题](book/Appendix-Collection-Topics.md) + * [示例数据](book/Appendix-Collection-Topics.md#示例数据) + * [List行为](book/Appendix-Collection-Topics.md#List行为) + * [Set行为](book/Appendix-Collection-Topics.md#Set行为) + * [在Map中使用函数式操作](book/Appendix-Collection-Topics.md#在Map中使用函数式操作) + * [选择Map片段](book/Appendix-Collection-Topics.md#选择Map片段) + * [填充集合](book/Appendix-Collection-Topics.md#填充集合) + * [使用享元(Flyweight)自定义Collection和Map](book/Appendix-Collection-Topics.md#使用享元(Flyweight)自定义Collection和Map) + * [集合功能](book/Appendix-Collection-Topics.md#集合功能) + * [可选操作](book/Appendix-Collection-Topics.md#可选操作) + * [Set和存储顺序](book/Appendix-Collection-Topics.md#Set和存储顺序) + * [队列](book/Appendix-Collection-Topics.md#队列) + * [理解Map](book/Appendix-Collection-Topics.md#理解Map) + * [集合工具类](book/Appendix-Collection-Topics.md#集合工具类) + * [持有引用](book/Appendix-Collection-Topics.md#持有引用) + * [Java 1.0 / 1.1 的集合类](book/Appendix-Collection-Topics.md#Java-10-11-的集合类) + * [本章小结](book/Appendix-Collection-Topics.md#本章小结) + +### [附录:数据压缩](book/Appendix-Data-Compression.md) + * [使用 Gzip 简单压缩](book/Appendix-Data-Compression.md#使用-Gzip-简单压缩) + * [使用 zip 多文件存储](book/Appendix-Data-Compression.md#使用-zip-多文件存储) + * [Java 的 jar](book/Appendix-Data-Compression.md#Java-的-jar) + +### [附录:流式IO](book/Appendix-IO-Streams.md) + * [输入流类型](book/Appendix-IO-Streams.md#输入流类型) + * [输出流类型](book/Appendix-IO-Streams.md#输出流类型) + * [添加属性和有用的接口](book/Appendix-IO-Streams.md#添加属性和有用的接口) + * [Reader和Writer](book/Appendix-IO-Streams.md#Reader和Writer) + * [RandomAccessFile类](book/Appendix-IO-Streams.md#RandomAccessFile类) + * [IO流典型用途](book/Appendix-IO-Streams.md#IO流典型用途) + * [本章小结](book/Appendix-IO-Streams.md#本章小结) + +### [附录:文档注释](book/Appendix-Javadoc.md) + * [句法规则](book/Appendix-Javadoc.md#句法规则) + * [内嵌 HTML](book/Appendix-Javadoc.md#内嵌-HTML) + * [示例标签](book/Appendix-Javadoc.md#示例标签) + * [文档示例](book/Appendix-Javadoc.md#文档示例) + +### [附录:并发底层原理](book/Appendix-Low-Level-Concurrency.md) + * [什么是线程?](book/Appendix-Low-Level-Concurrency.md#什么是线程?) + * [异常捕获](book/Appendix-Low-Level-Concurrency.md#异常捕获) + * [资源共享](book/Appendix-Low-Level-Concurrency.md#资源共享) + * [volatile 关键字](book/Appendix-Low-Level-Concurrency.md#volatile-关键字) + * [原子性](book/Appendix-Low-Level-Concurrency.md#原子性) + * [临界区](book/Appendix-Low-Level-Concurrency.md#临界区) + * [库组件](book/Appendix-Low-Level-Concurrency.md#库组件) + * [本章小结](book/Appendix-Low-Level-Concurrency.md#本章小结) + +### [附录:新IO](book/Appendix-New-IO.md) + * [ByteBuffer](book/Appendix-New-IO.md#ByteBuffer) + * [数据转换](book/Appendix-New-IO.md#数据转换) + * [基本类型获取](book/Appendix-New-IO.md#基本类型获取) + * [视图缓冲区](book/Appendix-New-IO.md#视图缓冲区) + * [缓冲区数据操作](book/Appendix-New-IO.md#缓冲区数据操作) + * [ 内存映射文件](book/Appendix-New-IO.md#-内存映射文件) + * [文件锁定](book/Appendix-New-IO.md#文件锁定) + +### [附录:对象序列化](book/Appendix-Object-Serialization.md) + * [查找类](book/Appendix-Object-Serialization.md#查找类) + * [控制序列化](book/Appendix-Object-Serialization.md#控制序列化) + * [使用持久化](book/Appendix-Object-Serialization.md#使用持久化) + * [XML](book/Appendix-Object-Serialization.md#XML) + +### [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md) + * [传递引用](book/Appendix-Passing-and-Returning-Objects.md#传递引用) + * [本地拷贝](book/Appendix-Passing-and-Returning-Objects.md#本地拷贝) + * [控制克隆](book/Appendix-Passing-and-Returning-Objects.md#控制克隆) + * [不可变类](book/Appendix-Passing-and-Returning-Objects.md#不可变类) + * [本章小结](book/Appendix-Passing-and-Returning-Objects.md#本章小结) + +### [附录:编程指南](book/Appendix-Programming-Guidelines.md) + * [设计](book/Appendix-Programming-Guidelines.md#设计) + * [实现](book/Appendix-Programming-Guidelines.md#实现) + +### [附录:标准IO](book/Appendix-Standard-IO.md) + * [从标准输入中读取](book/Appendix-Standard-IO.md#从标准输入中读取) + * [将 System.out 转换成 PrintWriter](book/Appendix-Standard-IO.md#将-Systemout-转换成-PrintWriter) + * [重定向标准 I/O](book/Appendix-Standard-IO.md#重定向标准-IO) + * [执行控制](book/Appendix-Standard-IO.md#执行控制) + +### [附录:补充](book/Appendix-Supplements.md) + * [可下载的补充](book/Appendix-Supplements.md#可下载的补充) + * [通过Thinking-in-C来巩固Java基础](book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础) + * [Hand-On Java 电子演示文稿](book/Appendix-Supplements.md#Hand-On-Java-电子演示文稿) + +### [附录:C++和Java的优良传统](book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) + +### [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md) + * [equals规范](book/Appendix-Understanding-equals-and-hashCode.md#equals规范) + * [哈希和哈希码](book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码) + * [调优 HashMap](book/Appendix-Understanding-equals-and-hashCode.md#调优-HashMap) + +### [词汇表](book/GLOSSARY.md) \ No newline at end of file diff --git a/docs/book/05-Control-Flow.md b/docs/book/05-Control-Flow.md old mode 100644 new mode 100755 index 854d6dc5..c12ac689 --- a/docs/book/05-Control-Flow.md +++ b/docs/book/05-Control-Flow.md @@ -235,7 +235,7 @@ i = 4 j = 8 上例中 **int** 类型声明包含了 `i` 和 `j`。实际上,在初始化部分我们可以定义任意数量的同类型变量。**注意**:在 Java 中,仅允许 **for** 循环在控制表达式中定义变量。 我们不能将此方法与其他的循环语句和选择语句中一起使用。同时,我们可以看到:无论在初始化还是在步进部分,语句都是顺序执行的。 -## for-in 语法 +## for-in 语法 Java 5 引入了更为简洁的“增强版 **for** 循环”语法来操纵数组和集合。(更多细节,可参考 [数组](./21-Arrays.md) 和 [集合](./12-Collections.md) 章节内容)。大部分文档也称其为 **for-each** 语法,但因为了不与 Java 8 新添的 `forEach()` 产生混淆,因此我称之为 **for-in** 循环。 (Python 已有类似的先例,如:**for x in sequence**)。**注意**:你可能会在其他地方看到不同叫法。 diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md old mode 100644 new mode 100755 diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index 49326b79..269fc785 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -771,7 +771,7 @@ public class Stack { } ``` -这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 **** 告诉编译器这是一个参数化类型,而其中的类型参数 **T** 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 **T** 类型对象的 **Stack** 。” **Stack** 是使用 **ArrayDeque** 实现的,而 **ArrayDeque** 也被告知它将持有 **T** 类型对象。注意, `push()` 接受类型为 **T** 的对象,而 `peek()` 和 `pop()` 返回类型为 **T** 的对象。 `peek()` 方法将返回栈顶元素,但并不将其从栈顶删除,而 `pop()` 删除并返回顶部元素。 +这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 **\** 告诉编译器这是一个参数化类型,而其中的类型参数 **T** 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 **T** 类型对象的 **Stack** 。” **Stack** 是使用 **ArrayDeque** 实现的,而 **ArrayDeque** 也被告知它将持有 **T** 类型对象。注意, `push()` 接受类型为 **T** 的对象,而 `peek()` 和 `pop()` 返回类型为 **T** 的对象。 `peek()` 方法将返回栈顶元素,但并不将其从栈顶删除,而 `pop()` 删除并返回顶部元素。 如果只需要栈的行为,那么使用继承是不合适的,因为这将产生一个具有 **ArrayDeque** 的其它所有方法的类(在[附录:集合主题]()中将会看到, **Java 1.0** 设计者在创建 **java.util.Stack** 时,就犯了这个错误)。使用组合,可以选择要公开的方法以及如何命名它们。 diff --git a/docs/book/18-Strings.md b/docs/book/18-Strings.md old mode 100644 new mode 100755 index be0bca0b..151eed10 --- a/docs/book/18-Strings.md +++ b/docs/book/18-Strings.md @@ -47,7 +47,7 @@ String x = Immutable.upcase(s); -## `+` 的重载与 `StringBuilder` +## + 的重载与 StringBuilder `String` 对象是不可变的,你可以给一个 `String` 对象添加任意多的别名。因为 `String` 是只读的,所以指向它的任何引用都不可能修改它的值,因此,也就不会影响到其他引用。 不可变性会带来一定的效率问题。为 `String` 对象重载的 `+` 操作符就是一个例子。重载的意思是,一个操作符在用于特定的类时,被赋予了特殊的意义(用于 `String` 的 `+` 与 `+=` 是 Java 中仅有的两个重载过的操作符,Java 不允许程序员重载任何其他的操作符 [^1])。 @@ -273,7 +273,7 @@ import java.util.stream.*; public class InfiniteRecursion { @Override public String toString() { - return " InfiniteRecursion address: " + this + "\n" + return " InfiniteRecursion address: " + this + "\n"; } public static void main(String[] args) { Stream.generate(InfiniteRecursion::new) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md old mode 100644 new mode 100755 index f013b51a..6c51d949 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -84,7 +84,7 @@ Triangle.draw() 但是,有时你会碰到一些编程问题,在这些问题中如果你能知道某个泛化引用的具体类型,就可以把问题轻松解决。例如,假设我们允许用户将某些几何形状高亮显示,现在希望找到屏幕上所有高亮显示的三角形;或者,我们现在需要旋转所有图形,但是想跳过圆形(因为圆形旋转没有意义)。这时我们就希望知道 `Stream` 里边的形状具体是什么类型,而 Java 实际上也满足了我们的这种需求。使用 RTTI,我们可以查询某个 `Shape` 引用所指向对象的确切类型,然后选择或者剔除特例。 -## `Class` 对象 +## Class 对象 要理解 RTTI 在 Java 中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由称为 **`Class`对象** 的特殊对象完成的,它包含了与类有关的信息。实际上,`Class` 对象就是用来创建该类所有"常规"对象的。Java 使用 `Class` 对象来实现 RTTI,即便是类型转换这样的操作都是用 `Class` 对象实现的。不仅如此,`Class` 类还提供了很多使用 RTTI 的其它方式。 diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 00d25272..43bb33ba 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -4298,7 +4298,7 @@ test string 2 1494331663027 2 */ ``` -**Mixin** 类基本上是在使用*委托*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。 +**Mixin** 类基本上是在使用*委托*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。[^4] ### 使用装饰器模式 @@ -4539,7 +4539,7 @@ Clank! ``` 在 Python 和 C++ 中,**Dog** 和 **Robot** 没有任何共同的东西,只是碰巧有两个方法具有相同的签名。从类型的观点看,它们是完全不同的类型。但是,`perform()` 不关心其参数的具体类型,并且潜在类型机制允许它接受这两种类型的对象。 -C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,是 C++ 的模版名声欠佳的主要原因)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。潜在类型机制没有损害强类型机制。 +C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,是 C++ 的模版名声欠佳的主要原因)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。[^5]潜在类型机制没有损害强类型机制。 ### Go 中的潜在类型 @@ -5064,7 +5064,7 @@ public class Suppliers { } ``` -`create()` 为你创建一个新的 **Collection** 子类型,而 `fill()` 的第一个版本将元素放入 **Collection** 的现有子类型中。 请注意,还会返回传入的容器的确切类型,因此不会丢失类型信息。 +`create()` 为你创建一个新的 **Collection** 子类型,而 `fill()` 的第一个版本将元素放入 **Collection** 的现有子类型中。 请注意,还会返回传入的容器的确切类型,因此不会丢失类型信息。[^6] 前两种方法一般都受约束,只能与 **Collection** 子类型一起使用。`fill()` 的第二个版本适用于任何类型的 **holder** 。 它需要一个附加参数:未绑定方法引用 `adder. fill()` ,使用辅助潜在类型来使其与任何具有添加元素方法的 **holder** 类型一起使用。因为此未绑定方法 **adder** 必须带有一个参数(要添加到 **holder** 的元素),所以 **adder** 必须是 `BiConsumer ` ,其中 **H** 是要绑定到的 **holder** 对象的类型,而 **A** 是要被添加的绑定元素类型。 对 `accept()` 的调用将使用参数 a 调用对象 **holder** 上的未绑定方法 **holder**。 diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md old mode 100644 new mode 100755 index 0ff7dad6..979170cf --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2414,7 +2414,7 @@ After sorting: [[i = 21, j = 6], [i = 70, j = 7], [i = 41, j = 20] , ``` -## Arrays.sort()的使用 +## Arrays.sort 的使用 使用内置的排序方法,您可以对实现了 **Comparable** 接口或具有 **Comparator** 的任何对象数组 或 任何原生数组进行排序。这里我们生成一个随机字符串对象数组并对其排序: diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index 95b79088..1b585691 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -16,7 +16,7 @@ - **@Deprecated**:如果使用该注解的元素被调用,编译器就会发出警告信息。 - **@SuppressWarnings**:关闭不当的编译器警告信息。 - **@SafeVarargs**:在 Java 7 中加入用于禁止对具有泛型varargs参数的方法或构造函数的调用方发出警告。 -- **@FunctionalInterface**:Java 8 中加入用于表示类型声明为函数式接口 +- **@FunctionalInterface**:Java 8 中加入用于表示类型声明为函数式接口。 还有 5 种额外的注解类型用于创造新的注解。你将会在这一章学习它们。 @@ -45,7 +45,7 @@ public class Testable { } ``` -被注解标注的方法和其他的方法没有任何区别。在这个例子中,注解 `@Test` 可以和任何修饰符共同用于方法,诸如 **public**、**static** 或 **void**。从语法的角度上看,注解的使用方式和修饰符的使用方式一致。 +被注解标注的方法和其他方法没有任何区别。在这个例子中,注解 `@Test` 可以和任何修饰符共同用于方法,诸如 **public**、**static** 或 **void**。从语法的角度上看,注解和修饰符的使用方式是一致的。 ### 定义注解 @@ -61,7 +61,7 @@ import java.lang.annotation.*; public @interface Test {} ``` -除了 @ 符号之外, `@Test` 的定义看起来更像一个空接口。注解的定义也需要一些元注解(meta-annoation),比如 `@Target` 和 `@Retention`。`@Target` 定义你的注解可以应用在哪里(例如是方法还是字段)。`@Retention` 定义了注解在哪里可用,在源代码中(SOURCE),class文件(CLASS)中或者是在运行时(RUNTIME)。 +除了 @ 符号之外, `@Test` 的定义看起来更像一个空接口。注解的定义也需要一些元注解(meta-annotation),比如 `@Target` 和 `@Retention`。`@Target` 定义你的注解可以应用在哪里(例如是方法还是字段)。`@Retention` 定义了注解在哪里可用,在源代码中(SOURCE),class文件(CLASS)中或者是在运行时(RUNTIME)。 注解通常会包含一些表示特定值的元素。当分析处理注解的时候,程序或工具可以利用这些值。注解的元素看起来就像接口的方法,但是可以为其指定默认值。 @@ -206,7 +206,7 @@ public @interface SimulatingNull { ### 生成外部文件 -当有些框架需要一些额外的信息才能与你的源代码协同工作,这种情况下注解就会变得十分有用。像 Enterprise JavaBeans (EJB3 之前)这样的技术,每一个 Bean 都需要需要大量的接口和部署描述文件,而这些就是“样板”文件。Web Service,自定义标签库以及对象/关系映射工具(例如 Toplink 和 Hibernate)通常都需要 XML 描述文件,而这些文件脱离于代码之外。除了定义 Java 类,程序员还必须忍受沉闷,重复的提供某些信息,例如类名和包名等已经在原始类中已经提供的信息。每当你使用外部描述文件时,他就拥有了一个类的两个独立信息源,这经常导致代码的同步问题。同时这也要求了为项目工作的程序员在知道如何编写 Java 程序的同时,也必须知道如何编辑描述文件。 +当有些框架需要一些额外的信息才能与你的源代码协同工作,这种情况下注解就会变得十分有用。像 Enterprise JavaBeans (EJB3 之前)这样的技术,每一个 Bean 都需要大量的接口和部署描述文件,而这些就是“样板”文件。Web Service,自定义标签库以及对象/关系映射工具(例如 Toplink 和 Hibernate)通常都需要 XML 描述文件,而这些文件脱离于代码之外。除了定义 Java 类,程序员还必须忍受沉闷,重复的提供某些信息,例如类名和包名等已经在原始类中提供过的信息。每当你使用外部描述文件时,他就拥有了一个类的两个独立信息源,这经常导致代码的同步问题。同时这也要求了为项目工作的程序员在知道如何编写 Java 程序的同时,也必须知道如何编辑描述文件。 假设你想提供一些基本的对象/关系映射功能,能够自动生成数据库表。你可以使用 XML 描述文件来指明类的名字、每个成员以及数据库映射的相关信息。但是,通过使用注解,你可以把所有信息都保存在 **JavaBean** 源文件中。为此你需要一些用于定义数据库表名称、数据库列以及将 SQL 类型映射到属性的注解。 @@ -306,7 +306,7 @@ public class Member { } ``` -类注解 **@DBTable** 注解给定了元素值 MEMBER,它将会作为标的名字。类的属性 **firstName** 和 **lastName** 都被注解为 **@SQLString** 类型并且给了默认元素值分别为 30 和 50。这些注解都有两个有趣的地方:首先,他们都使用了嵌入的 **@Constraints** 注解的默认值;其次,它们都是用了快捷方式特性。如果你在注解中定义了名为 **value** 的元素,并且在使用该注解时,**value** 为唯一一个需要赋值的元素,你就不需要使用名—值对的语法,你只需要在括号中给出 **value** 元素的值即可。这可以应用于任何合法类型的元素。这也限制了你必须将元素命名为 **value**,不过在上面的例子中,这样的注解语句也更易于理解: +类注解 **@DBTable** 注解给定了元素值 MEMBER,它将会作为表的名字。类的属性 **firstName** 和 **lastName** 都被注解为 **@SQLString** 类型并且给了默认元素值分别为 30 和 50。这些注解都有两个有趣的地方:首先,他们都使用了嵌入的 **@Constraints** 注解的默认值;其次,它们都是用了快捷方式特性。如果你在注解中定义了名为 **value** 的元素,并且在使用该注解时,**value** 为唯一一个需要赋值的元素,你就不需要使用名—值对的语法,你只需要在括号中给出 **value** 元素的值即可。这可以应用于任何合法类型的元素。这也限制了你必须将元素命名为 **value**,不过在上面的例子中,这样的注解语句也更易于理解: ```java @SQLString(30) @@ -320,7 +320,7 @@ public class Member { 可以使用多种不同的方式来定义自己的注解用于上述任务。例如,你可以使用一个单一的注解类 **@TableColumn**,它拥有一个 **enum** 元素,元素值定义了 **STRING**,**INTEGER**,**FLOAT** 等类型。这消除了每个 SQL 类型都需要定义一个 **@interface** 的负担,不过也使得用额外信息修饰 SQL 类型变的不可能,这些额外的信息例如长度或精度等,都可能是非常有用的。 -你也可以使用一个 **String** 类型的元素来描述实际的 SQL 类型,比如 “VARCHAR(30)” 或者 “INTEGER”。这使得你可以修饰 SQL 类型,但是这也将 Java 类型到 SQL 类型的映射绑在了一起,这不是一个好的设计。你并不想在数据库更改之后重新编译你的代码;如果我们只需要告诉注解处理器,我们正在使用的是什么“口味(favor)”的 SQL,然后注解助力器来为我们处理 SQL 类型的细节,那将是一个优雅的设计。 +你也可以使用一个 **String** 类型的元素来描述实际的 SQL 类型,比如 “VARCHAR(30)” 或者 “INTEGER”。这使得你可以修饰 SQL 类型,但是这也将 Java 类型到 SQL 类型的映射绑在了一起,这不是一个好的设计。你并不想在数据库更改之后重新编译你的代码;如果我们只需要告诉注解处理器,我们正在使用的是什么“口味(favor)”的 SQL,然后注解处理器来为我们处理 SQL 类型的细节,那将是一个优雅的设计。 第三种可行的方案是一起使用两个注解,**@Constraints** 和相应的 SQL 类型(例如,**@SQLInteger**)去注解同一个字段。这可能会让代码有些混乱,但是编译器允许你对同一个目标使用多个注解。在 Java 8,在使用多个注解的时候,你可以重复使用同一个注解。 @@ -459,7 +459,7 @@ CREATE TABLE MEMBER( 每一个你编写的注解都需要处理器,但是 **javac** 可以非常容易的将多个注解处理器合并在一起。你可以指定多个需要处理的类,并且你可以添加监听器用于监听注解处理完成后接到通知。 -本节中的示例将帮助你开始学习,但如果你必须深入学习,请做好反复学习,大量访问 Google 和StackOverflow 的准备。 +本节中的示例将帮助你开始学习,但如果你必须深入学习,请做好反复学习,大量访问 Google 和 StackOverflow 的准备。 ### 最简单的处理器 @@ -576,11 +576,11 @@ public class SimpleProcessor } ``` -(旧的,失效的)**apt** 版本的处理器需要额外的方法来确定支持哪些注解以及支持的 Java 版本。不过,你现在可以简单的使用 **@SupportedAnnotationTypes** 和 **@SupportedSourceVersion** 注解(这是一个很好的示例关于注解如何简化你的代码)。 +(旧的,失效的)**apt** 版本的处理器需要额外的方法来确定支持哪些注解以及支持的 Java 版本。不过,你现在可以简单的使用 **@SupportedAnnotationTypes** 和 **@SupportedSourceVersion** 注解(这是一个很好的用注解简化代码的示例)。 -你唯一需要实现的方法就是 `process()`,这里是所有行为发生的地方。第一个参数告诉你哪个注解是存在的,第二个参数保留了剩余信息。我们所做的事情只是打印了注解(这里只存在一个),可以看 **TypeElement** 文档中的其他行为。通过使用 `process()` 的第二个操作,我们循环所有被 **@Simple** 注解的元素,并且针对每一个元素调用我们的 `display()` 方法。所有 **Element** 展示了本身的基本信息;例如,`getModifiers()` 告诉你它是否为 **public** 和 **static** 的。 +你唯一需要实现的方法就是 `process()`,这里是所有行为发生的地方。第一个参数告诉你哪个注解是存在的,第二个参数保留了剩余信息。我们所做的事情只是打印了注解(这里只存在一个),可以看 **TypeElement** 文档中的其他行为。通过使用 `process()` 的第二个操作,我们循环所有被 **@Simple** 注解的元素,并且针对每一个元素调用我们的 `display()` 方法。所有 **Element** 展示了自身的基本信息;例如,`getModifiers()` 告诉你它是否为 **public** 和 **static** 。 -**Element** 只能执行那些编译器解析的所有基本对象共有的操作,而类和方法之类的东西有额外的信息需要提取。所以(如果你阅读了正确的文档,但是我没有在任何文档中找到——我不得不通过 StackOverflow 寻找线索)你检查它是哪种 **ElementKind**,然后将其向下转换为更具体的元素类型,注入针对 CLASS 的 TypeElement 和 针对 METHOD 的ExecutableElement。此时,可以为这些元素调用其他方法。 +**Element** 只能执行那些编译器解析的所有基本对象共有的操作,而类和方法之类的东西有额外的信息需要提取。所以(如果你阅读了正确的文档,但是我没有在任何文档中找到——我不得不通过 StackOverflow 寻找线索)你检查它是哪种 **ElementKind**,然后将其向下转换为更具体的元素类型,注入针对 CLASS 的 TypeElement 和针对 METHOD 的ExecutableElement。此时,可以为这些元素调用其他方法。 动态向下转型(在编译期不进行检查)并不像是 Java 的做事方式,这非常不直观这也是为什么我从未想过要这样做事。相反,我花了好几天的时间,试图发现你应该如何访问这些信息,而这些信息至少在某种程度上是用不起作用的恰当方法简单明了的。我还没有遇到任何东西说上面是规范的形式,但在我看来是。 diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md old mode 100644 new mode 100755 index 603eb9e9..668db583 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -3,16 +3,16 @@ # 第二十四章 并发编程 ->爱丽丝:“我可不想到疯子中间去” +> 爱丽丝:“我可不想到疯子中间去” > ->猫咪:“啊,那没辙了,我们这都是疯子。我疯了,你也疯了” +> 猫咪:“啊,那没辙了,我们这都是疯子。我疯了,你也疯了” > ->爱丽丝:“你怎么知道我疯了”。 +> 爱丽丝:“你怎么知道我疯了”。 > ->猫咪:“你一定是疯了,否则你就不会来这儿” ——爱丽丝梦游仙境 第6章。 +> 猫咪:“你一定是疯了,否则你就不会来这儿” ——爱丽丝梦游仙境 第 6 章。 -在本章之前,我们惯用一种简单顺序的叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果将值设置为5,再看时它已变成47的话,结果就很匪夷所思了。 +在本章之前,我们惯用一种简单顺序的叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果将值设置为 5,再看时它已变成 47 的话,结果就很匪夷所思了。 现在,我们来到了陌生的并发世界。这样的结果一点都不奇怪,因为你原来信赖的一切都不再可靠。它可能有效,也可能无效。更可能得是,它在某些情况下会起作用,而在另一些情况下则不会。只有了解了这些情况,我们才能正确地行事。 @@ -20,11 +20,11 @@ 假设我们处在多条故事线并行的间谍小说里,非单一意识流地叙事:第一个间谍在岩石底留下了微缩胶片。当第二个间谍来取时,胶片可能已被第三个间谍拿走。小说并没有交代此处的细节。所以直到故事结尾,我们都没搞清楚到底发生了什么。 -构建并发程序好比玩[搭积木](https://en.wikipedia.org/wiki/Jenga)游戏。每拉出一块放在塔顶时都有崩塌的可能。每个积木塔和应用程序都是独一无二的,有着自己的作用。你在某个系统构建中学到的知识并不一定适用于下一个系统。 +构建并发程序好比玩[搭积木 ](https://en.wikipedia.org/wiki/Jenga) 游戏。每拉出一块放在塔顶时都有崩塌的可能。每个积木塔和应用程序都是独一无二的,有着自己的作用。你在某个系统构建中学到的知识并不一定适用于下一个系统。 本章是对并发概念最基本的介绍。虽然我们用到了现代的 Java 8 工具来演示原理,但还远未及全面论述并发。我的目标是为你提供足够的基础知识,使你能够把握问题的复杂性和危险性,从而安全地渡过这片鲨鱼肆虐的困难水域。 -更多繁琐和底层的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须要了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了 JVM 的内部工作方式,包括线程。 +更多繁琐和底层的细节,请参阅附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须要了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了 JVM 的内部工作方式,包括线程。 @@ -42,7 +42,7 @@ “并发”通常表示:不止一个任务正在执行。而“并行”几乎总是代表:不止一个任务同时执行。现在你能看到问题所在了吗?“并行”也有不止一个任务正在执行的语义在里面。区别就在于细节:究竟是怎么“执行”的。此外,还有一些场景重叠:为并行编写的程序有时在单处理器上运行,而一些并发编程系统可以利用多处理器。 -还有另一种方法,在减速发生的地方写下定义(原文Here’s another approach, writing the definitions around where the +还有另一种方法,在减速发生的地方写下定义(原文 Here’s another approach, writing the definitions around where the slowdown occurs): **并发** @@ -53,36 +53,36 @@ slowdown occurs): 同时在多个位置完成多任务。这解决了所谓的 CPU 密集型问题:将程序分为多部分,在多个处理器上同时处理不同部分来加快程序执行效率。 -上面的定义说明了这两个术语令人困惑的原因:两者的核心都是“同时完成多个任务”,不过并行增加了跨多个处理器的分布。更重要的是,它们可以解决不同类型的问题:并行可能对解决I / O密集型问题没有任何好处,因为问题不在于程序的整体执行速度,而在于I/O阻塞。而尝试在单个处理器上使用并发来解决计算密集型问题也可能是浪费时间。两种方法都试图在更短的时间内完成更多工作,但是它们实现加速的方式有所不同,这取决于问题施加的约束。 +上面的定义说明了这两个术语令人困惑的原因:两者的核心都是“同时完成多个任务”,不过并行增加了跨多个处理器的分布。更重要的是,它们可以解决不同类型的问题:并行可能对解决 I / O 密集型问题没有任何好处,因为问题不在于程序的整体执行速度,而在于 I/O 阻塞。而尝试在单个处理器上使用并发来解决计算密集型问题也可能是浪费时间。两种方法都试图在更短的时间内完成更多工作,但是它们实现加速的方式有所不同,这取决于问题施加的约束。 -这两个概念混合在一起的一个主要原因是包括Java在内的许多编程语言使用相同的机制 - **线程**来实现并发和并行。 +这两个概念混合在一起的一个主要原因是包括 Java 在内的许多编程语言使用相同的机制 - **线程**来实现并发和并行。 我们甚至可以尝试以更细的粒度去进行定义(然而这并不是标准化的术语): -- **纯并发**:仍然在单个CPU上运行任务。纯并发系统比顺序系统更快地产生结果,但是它的运行速度不会因为处理器的增加而变得更快。 +- **纯并发**:仍然在单个 CPU 上运行任务。纯并发系统比顺序系统更快地产生结果,但是它的运行速度不会因为处理器的增加而变得更快。 - **并发-并行**:使用并发技术,结果程序可以利用更多处理器更快地产生结果。 -- **并行-并发**:使用并行编程技术编写,如果只有一个处理器,结果程序仍然可以运行(Java 8 **Streams**就是一个很好的例子)。 +- **并行-并发**:使用并行编程技术编写,如果只有一个处理器,结果程序仍然可以运行(Java 8 **Streams** 就是一个很好的例子)。 - **纯并行**:除非有多个处理器,否则不会运行。 在某些情况下,这可能是一个有用的分类法。 -支持并发性的语言和库似乎是[抽象泄露(Leaky Abstraction)](https://en.wikipedia.org/wiki/Leaky_abstraction)一词的完美候选。抽象的目标是“抽象出”那些对于手头想法不重要的东西,以屏蔽不必要的细节。如果抽象是有漏洞的,那些碎片和细节就会不断重新声明自己是重要的,无论你废了多少功夫来隐藏它们。 +支持并发性的语言和库似乎是[抽象泄露(Leaky Abstraction)](https://en.wikipedia.org/wiki/Leaky_abstraction) 一词的完美候选。抽象的目标是“抽象出”那些对于手头想法不重要的东西,以屏蔽不必要的细节。如果抽象是有漏洞的,那些碎片和细节就会不断重新声明自己是重要的,无论你废了多少功夫来隐藏它们。 -我开始怀疑是否真的有高度抽象。因为当编写这类程序时,底层的系统、工具,甚至是关于CPU缓存如何工作的细节,都永远不会被屏蔽。最后,如果你非常小心,你创作的东西在特定的情况下工作,但在其他情况下不工作。有时是两台机器的配置方式不同,有时是程序的估计负载不同。这不是Java特有的 - 这是并发和并行编程的本质。 +我开始怀疑是否真的有高度抽象。因为当编写这类程序时,底层的系统、工具,甚至是关于 CPU 缓存如何工作的细节,都永远不会被屏蔽。最后,如果你非常小心,你创作的东西在特定的情况下工作,但在其他情况下不工作。有时是两台机器的配置方式不同,有时是程序的估计负载不同。这不是 Java 特有的 - 这是并发和并行编程的本质。 -你可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional)语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果该系统没有被正确地调整,并且输入速率也没有被正确地估计或限制(在不同的情况下,限制意味着具有不同的影响的不同东西),该队列要么被填满并阻塞,要么溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式。 +你可能会认为[纯函数式 ](https://en.wikipedia.org/wiki/Purely_functional) 语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果该系统没有被正确地调整,并且输入速率也没有被正确地估计或限制(在不同的情况下,限制意味着具有不同的影响的不同东西),该队列要么被填满并阻塞,要么溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式。 ### 并发的新定义 几十年来,我一直在努力解决各种形式的并发问题,其中一个最大的挑战一直是简单地定义它。在撰写本章的过程中,我终于有了这样的洞察力,我认为可以定义它: ->**并发性是一系列性能技术,专注于减少等待** +>** 并发性是一系列性能技术,专注于减少等待** 这实际上是一个相当复杂的表述,所以我将其分解: - 这是一个集合:包含许多不同的方法来解决这个问题。这是使定义并发性如此具有挑战性的问题之一,因为技术差异很大。 -- 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在Java中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。 -- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待发生时产生效益。如果你发起I/O请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且没有任务需要等待其他任务,那么尝试提高吞吐量是没有意义的。并发的唯一机会是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。 +- 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在 Java 中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。 +- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待发生时产生效益。如果你发起 I/O 请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且没有任务需要等待其他任务,那么尝试提高吞吐量是没有意义的。并发的唯一机会是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。 值得强调的是,这个定义的有效性取决于“等待”这个词。如果没有什么可以等待,那就没有机会去加速。如果有什么东西在等待,那么就会有很多方法可以加快速度,这取决于多种因素,包括系统运行的配置,你要解决的问题类型以及其他许多问题。 @@ -95,7 +95,7 @@ slowdown occurs): 现在假设你有一个奇怪的超能力。你可以将自己一分为二,然后在继续前进的同时将另一半送到另一个走廊。每当你在走廊或楼梯上遇到分隔到下一层时,你都会重复这个分裂的技巧。最终,整个建筑中的每个走廊的终点都有一个你。 -每个走廊都有一千个房间。你的超能力变得有点弱,所以你只能分裂出50个自己来搜索这间房间。 +每个走廊都有一千个房间。你的超能力变得有点弱,所以你只能分裂出 50 个自己来搜索这间房间。 一旦克隆体进入房间,它必须搜索房间的每个角落。这时它切换到了第二种超能力。它分裂成了一百万个纳米机器人,每个机器人都会飞到或爬到房间里一些看不见的地方。你不需要了解这种功能 - 一旦你开启它就会自动工作。在他们自己的控制下,纳米机器人开始行动,搜索房间然后回来重新组装成你,突然间,你获得了寻找的物品是否在房间内的消息。 @@ -103,7 +103,7 @@ slowdown occurs): 以下是其中一个漏洞:在理想的世界中,每次克隆自己时,你还会复制硬件处理器来运行该克隆。但当然不会发生这种情况 - 你的机器上可能有四个或八个处理器(通常在写入时)。你可能还有更多,并且仍有许多情况只有一个处理器。在抽象的讨论中,物理处理器的分配方式不仅可以泄漏,甚至可以支配你的决策 -让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人回答。如果我们每个搜索者有一个处理器,这没有问题 - 处理器只是空闲,直到门被回答。但是如果我们只有8个处理器和数千个搜索者,我们不希望处理器仅仅因为某个搜索者恰好在等待回答中被锁住而闲置下来。相反,我们希望将处理器应用于可以真正执行工作的搜索者身上,因此需要将处理器从一个任务切换到另一个任务的机制。 +让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人回答。如果我们每个搜索者有一个处理器,这没有问题 - 处理器只是空闲,直到门被回答。但是如果我们只有 8 个处理器和数千个搜索者,我们不希望处理器仅仅因为某个搜索者恰好在等待回答中被锁住而闲置下来。相反,我们希望将处理器应用于可以真正执行工作的搜索者身上,因此需要将处理器从一个任务切换到另一个任务的机制。 许多模型能够有效地隐藏处理器的数量,并允许你假装你的数量非常大。但是有些情况会发生故障的时候,你必须知道处理器的数量,以便你可以解决这个问题。 @@ -126,7 +126,7 @@ slowdown occurs): 当“同时”执行的任务相互干扰时,会出现问题。它可以以如此微妙和偶然的方式发生,可能公平地说,并发性“可以说是确定性的,但实际上是非确定性的。”也就是说,你可以假设编写通过维护和代码检查正常工作的并发程序。然而,在实践中,我们编写的并发程序似乎都能正常工作,但是在适当的条件下,将会失败。这些情况可能永远不会发生,或者在你在测试期间几乎很难发现它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。 这是学习并发中最强有力的论点之一:如果你忽略它,你可能会受伤。 -因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管Java 8在并发性方面做出了很大改进,但仍然没有像编译时验证(compile-time verification)或受检查的异常(checked exceptions)那样的安全网来告诉你何时出现错误。通过并发,你只能依靠自己,只有知识渊博,保持怀疑和积极进取的人,才能用Java编写可靠的并发代码。 +因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管 Java 8 在并发性方面做出了很大改进,但仍然没有像编译时验证 (compile-time verification) 或受检查的异常 (checked exceptions) 那样的安全网来告诉你何时出现错误。通过并发,你只能依靠自己,只有知识渊博,保持怀疑和积极进取的人,才能用 Java 编写可靠的并发代码。 @@ -136,28 +136,28 @@ slowdown occurs): 速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解成碎片并在一个单独的处理器上运行每个部分。由于我们能够提高时钟速度流(至少对于传统芯片),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使你的程序运行得更快,你必须学会如何利用那些额外的处理器,这是并发性给你的一个建议。 -使用多处理器机器,可以在这些处理器之间分配多个任务,这可以显着提高吞吐量。强大的多处理器Web服务器通常就是这种情况,它可以在程序中为CPU分配大量用户请求,每个请求分配一个线程。 +使用多处理器机器,可以在这些处理器之间分配多个任务,这可以显着提高吞吐量。强大的多处理器 Web 服务器通常就是这种情况,它可以在程序中为 CPU 分配大量用户请求,每个请求分配一个线程。 但是,并发性通常可以提高在单个处理器上运行的程序的性能。这听起来有点违反直觉。如果考虑一下,由于上下文切换的成本增加(从一个任务更改为另一个任务),在单个处理器上运行的并发程序实际上应该比程序的所有部分顺序运行具有更多的开销。在表面上,将程序的所有部分作为单个任务运行并节省上下文切换的成本似乎更便宜。 -可以产生影响的问题是阻塞。如果你的程序中的一个任务由于程序控制之外的某些条件(通常是I/O)而无法继续,我们会说任务或线程阻塞(在我们的科幻故事中,克隆体已敲门而且是等待它打开)。如果没有并发性,整个程序就会停止,直到外部条件发生变化。但是,如果使用并发编写程序,则当一个任务被阻止时,程序中的其他任务可以继续执行,因此程序继续向前移动。实际上,从性能的角度来看,在单处理器机器上使用并发是没有意义的,除非其中一个任务可能阻塞。 +可以产生影响的问题是阻塞。如果你的程序中的一个任务由于程序控制之外的某些条件(通常是 I/O)而无法继续,我们会说任务或线程阻塞(在我们的科幻故事中,克隆体已敲门而且是等待它打开)。如果没有并发性,整个程序就会停止,直到外部条件发生变化。但是,如果使用并发编写程序,则当一个任务被阻止时,程序中的其他任务可以继续执行,因此程序继续向前移动。实际上,从性能的角度来看,在单处理器机器上使用并发是没有意义的,除非其中一个任务可能阻塞。 单处理器系统中性能改进的一个常见例子是事件驱动编程,特别是用户界面编程。考虑一个程序执行一些长时间运行操作,从而最终忽略用户输入和无响应。如果你有一个“退出”按钮,你不想在你编写的每段代码中轮询它。这会产生笨拙的代码,无法保证程序员不会忘记执行检查。没有并发性,生成响应式用户界面的唯一方法是让所有任务定期检查用户输入。通过创建单独的执行线程来响应用户输入,该程序保证了一定程度的响应。 -实现并发的直接方法是在操作系统级别,使用与线程不同的进程。进程是一个在自己的地址空间内运行的自包含程序。进程很有吸引力,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程共享内存和I/O等资源,因此编写多线程程序时遇到的困难是在不同的线程驱动的任务之间协调这些资源,一次不能通过多个任务访问它们。 +实现并发的直接方法是在操作系统级别,使用与线程不同的进程。进程是一个在自己的地址空间内运行的自包含程序。进程很有吸引力,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程共享内存和 I/O 等资源,因此编写多线程程序时遇到的困难是在不同的线程驱动的任务之间协调这些资源,一次不能通过多个任务访问它们。 有些人甚至提倡将进程作为并发的唯一合理方法[^1],但不幸的是,通常存在数量和开销限制,从而阻止了在并发范围内的适用性(最终你会习惯标准的并发限制,“这种方法适用于一些情况但不适用于其他情况”) -一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果你发现程序的某一部分必须大量使用并发性并且你在尝试构建该部分时遇到了过多的问题,那么你可能会考虑使用专用并发语言创建程序的那一部分。 +一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang 就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果你发现程序的某一部分必须大量使用并发性并且你在尝试构建该部分时遇到了过多的问题,那么你可能会考虑使用专用并发语言创建程序的那一部分。 -Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分配外部进程,线程在执行程序所代表的单个进程中创建任务交换。 +Java 采用了更传统的方法[^2],即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分配外部进程,线程在执行程序所代表的单个进程中创建任务交换。 并发性会带来成本,包括复杂性成本,但可以通过程序设计,资源平衡和用户便利性的改进来抵消。通常,并发性使你能够创建更加松散耦合的设计;除此以外,你必须特别关注那些使用了并发操作的代码。 ## 四句格言 -在经历了多年的Java并发之后,我总结了以下四个格言: +在经历了多年的 Java 并发之后,我总结了以下四个格言: >1.不要这样做 > >2.没有什么是真的,一切可能都有问题 @@ -166,7 +166,7 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 > >4.你仍然必须理解它 -这些格言专门针对Java的并发设计问题,尽管它们也可以适用于其他一些语言。但是,确实存在旨在防止这些问题的语言。 +这些格言专门针对 Java 的并发设计问题,尽管它们也可以适用于其他一些语言。但是,确实存在旨在防止这些问题的语言。 ### 1.不要这样做 @@ -186,7 +186,7 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 在非并发程序中你可以忽略的各种事情在并发程序中突然变得非常重要。例如,你必须知道处理器缓存以及保持本地缓存与主内存一致的问题。你必须深入了解对象构造的复杂性,以便你的构造器不会意外地将数据暴露给其他线程进行更改。问题还有很多。 -因为这些主题太复杂,本章无法为你提供更专业的知识(再次参见Java Concurrency in Practice),但你必须了解它们。 +因为这些主题太复杂,本章无法为你提供更专业的知识(再次参见 Java Concurrency in Practice),但你必须了解它们。 ### 3.它起作用,并不意味着它没有问题 @@ -197,31 +197,31 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 - 你通常不能编写有用的测试,因此你必须依靠代码检查结合深入的并发知识来发现错误。 - 即使是有效的程序也只能在其设计参数下工作。当超出这些设计参数时,大多数并发程序会以某种方式失败。 -在其他 Java 主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect)效应的认知偏差,可以概括为“无知者无畏。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。 +在其他 Java 主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect) 效应的认知偏差,可以概括为“无知者无畏。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。 我自己的经验是,无论你是多么确定你的代码是线程安全的,它可能已经无效了。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念让你意识到你编写的大多数内容实际上都容易受到并发错误的影响。当某些内容不正确时,编译器不会告诉你。为了使它正确,你必须在研究代码前了解所有并发问题。 -在Java的所有非并发领域,“没有明显的错误和没有明显的编译错误”似乎意味着一切都好。对于并发,它没有任何意义。在这种情况你最糟糕的表现就是“自信”。 +在 Java 的所有非并发领域,“没有明显的错误和没有明显的编译错误”似乎意味着一切都好。对于并发,它没有任何意义。在这种情况你最糟糕的表现就是“自信”。 ### 4.你必须仍然理解 -在格言1-3之后,你可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续避免它。 +在格言 1-3 之后,你可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续避免它。 -这是一种理性的反应。你可能知道其他编程语言更好地设计用于构建并发程序 - 甚至是在JVM上运行的程序(从而提供与Java的轻松通信),例如Clojure或Scala。为什么不用这些语言编写并发部分并将Java用于其他所有部分呢? +这是一种理性的反应。你可能知道其他编程语言更好地设计用于构建并发程序 - 甚至是在 JVM 上运行的程序(从而提供与 Java 的轻松通信),例如 Clojure 或 Scala。为什么不用这些语言编写并发部分并将 Java 用于其他所有部分呢? 唉,你不能轻易逃脱: -- 即使你从未明确地创建一个线程,你可能使用的框架 - 例如,Swing图形用户界面(GUI)库,或者像**Timer** class那样简单的东西。 +- 即使你从未明确地创建一个线程,你可能使用的框架 - 例如,Swing 图形用户界面(GUI)库,或者像 **Timer** class 那样简单的东西。 - 这是最糟糕的事情:当你创建组件时,你必须假设这些组件可能在多线程环境中重用。即使你的解决方案是放弃并声明你的组件“不是线程安全的”,你仍然必须知道这样的声明是重要的,它是什么意思? 人们有时会认为并发性太难,不能包含在介绍该语言的书中。他们认为并发是一个可以独立对待的独立主题,并且它在日常编程中出现的少数情况(例如图形用户界面)可以用特殊的习语来处理。如果你可以避免它,为什么要介绍这样的复杂的主题。 -唉,如果只是这样的话,那就太好了。但不幸的是,你无法选择何时在Java程序中出现线程。仅仅你从未写过自己的线程,并不意味着你可以避免编写线程代码。例如,Web系统是最常见的Java应用程序之一,本质上是多线程的Web服务器通常包含多个处理器,而并行性是利用这些处理器的理想方式。就像这样的系统看起来那么简单,你必须理解并发才能正确地编写它。 +唉,如果只是这样的话,那就太好了。但不幸的是,你无法选择何时在 Java 程序中出现线程。仅仅你从未写过自己的线程,并不意味着你可以避免编写线程代码。例如,Web 系统是最常见的 Java 应用程序之一,本质上是多线程的 Web 服务器通常包含多个处理器,而并行性是利用这些处理器的理想方式。就像这样的系统看起来那么简单,你必须理解并发才能正确地编写它。 -Java是一种多线程语言,不管你有没有意识到并发问题,它就在那里。因此,有许多Java程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。 +Java 是一种多线程语言,不管你有没有意识到并发问题,它就在那里。因此,有许多 Java 程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。 -## 残酷的真相 +## 残酷的真相 当人类开始烹饪他们的食物时,他们大大减少了他们的身体分解和消化食物所需的能量。烹饪创造了一个“外化的胃”,从而释放出能量去发展其他的能力。火的使用促成了文明。 @@ -229,51 +229,51 @@ Java是一种多线程语言,不管你有没有意识到并发问题,它就 有了这种根本性的人类变化,看到许多破坏和失败的实验并不令人惊讶。实际上,进化依赖于无数的实验,其中大多数都失败了。这些实验是向前发展的必要条件。 -Java是在充满自信,热情和睿智的氛围中创建的。在发明一种编程语言时,很容易感觉语言的初始可塑性会持续存在一样,你可以把某些东西拿出来,如果不能解决问题,那么就修复它。编程语言以这种方式是独一无二的 - 它们经历了类似水的改变:气态,液态和最终的固态。在气态阶段,灵活性似乎是无限的,并且很容易认为它总是那样。一旦人们开始使用你的语言,变化就会变得更加严重,环境变得更加粘稠。语言设计的过程本身就是一门艺术。 +Java 是在充满自信,热情和睿智的氛围中创建的。在发明一种编程语言时,很容易感觉语言的初始可塑性会持续存在一样,你可以把某些东西拿出来,如果不能解决问题,那么就修复它。编程语言以这种方式是独一无二的 - 它们经历了类似水的改变:气态,液态和最终的固态。在气态阶段,灵活性似乎是无限的,并且很容易认为它总是那样。一旦人们开始使用你的语言,变化就会变得更加严重,环境变得更加粘稠。语言设计的过程本身就是一门艺术。 -紧迫感来自互联网的最初兴起。它似乎是一场比赛,第一个通过起跑线的人将“获胜”(事实上,Java,JavaScript和PHP等语言的流行程度可以证明这一点)。唉,通过匆忙设计语言而产生的认知负荷和技术债务最终会赶上我们。 +紧迫感来自互联网的最初兴起。它似乎是一场比赛,第一个通过起跑线的人将“获胜”(事实上,Java,JavaScript 和 PHP 等语言的流行程度可以证明这一点)。唉,通过匆忙设计语言而产生的认知负荷和技术债务最终会赶上我们。 -[Turing completeness](https://en.wikipedia.org/wiki/Turing_completeness)是不足够的;语言需要更多的东西:它们必须能够创造性地表达,而不是用不必要的东西来衡量我们。解放我们的心理能力只是为了扭转并再次陷入困境,这是毫无意义的。我承认,尽管存在这些问题,我们已经完成了令人惊奇的事情,但我也知道如果没有这些问题我们能做得更多。 +[Turing completeness](https://en.wikipedia.org/wiki/Turing_completeness) 是不足够的;语言需要更多的东西:它们必须能够创造性地表达,而不是用不必要的东西来衡量我们。解放我们的心理能力只是为了扭转并再次陷入困境,这是毫无意义的。我承认,尽管存在这些问题,我们已经完成了令人惊奇的事情,但我也知道如果没有这些问题我们能做得更多。 -热情使原始Java设计师加入了一些似乎有必要的特性。信心(以及气态的初始语言)让他们认为任何问题随后都可以解决。在时间轴的某个地方,有人认为任何加入Java的东西是固定的和永久性的 -他们非常有信心,并相信第一个决定永远是正确的,因此我们看到Java的体系中充斥着糟糕的决策。其中一些决定最终没有什么后果;例如,你可以告诉人们不要使用Vector,但只能在语言中继续保留它以便对之前版本的支持。 +热情使原始 Java 设计师加入了一些似乎有必要的特性。信心(以及气态的初始语言)让他们认为任何问题随后都可以解决。在时间轴的某个地方,有人认为任何加入 Java 的东西是固定的和永久性的 -他们非常有信心,并相信第一个决定永远是正确的,因此我们看到 Java 的体系中充斥着糟糕的决策。其中一些决定最终没有什么后果;例如,你可以告诉人们不要使用 Vector,但只能在语言中继续保留它以便对之前版本的支持。 -线程包含在Java 1.0中。当然,对java来说支持并发是一个很基本的设计决定,该特性影响了这个语言的各个角落,我们很难想象以后在以后的版本添加它。公平地说,当时并不清楚基本的并发性是多少。像C这样的其他语言能够将线程视为一个附加功能,因此Java设计师也纷纷效仿,包括一个Thread类和必要的JVM支持(这比你想象的要复杂得多)。 +线程包含在 Java 1.0 中。当然,对 java 来说支持并发是一个很基本的设计决定,该特性影响了这个语言的各个角落,我们很难想象以后在以后的版本添加它。公平地说,当时并不清楚基本的并发性是多少。像 C 这样的其他语言能够将线程视为一个附加功能,因此 Java 设计师也纷纷效仿,包括一个 Thread 类和必要的 JVM 支持(这比你想象的要复杂得多)。 -C语言是面向过程语言,这限制了它的野心。这些限制使附加线程库合理。当采用原始模型并将其粘贴到复杂语言中时,Java的大规模扩展迅速暴露了基本问题。在Thread类中的许多方法的弃用以及后续的高级库浪潮中,这种情况变得明显,这些库试图提供更好的并发抽象。 +C 语言是面向过程语言,这限制了它的野心。这些限制使附加线程库合理。当采用原始模型并将其粘贴到复杂语言中时,Java 的大规模扩展迅速暴露了基本问题。在 Thread 类中的许多方法的弃用以及后续的高级库浪潮中,这种情况变得明显,这些库试图提供更好的并发抽象。 -不幸的是,为了在更高级别的语言中获得并发性,所有语言功能都会受到影响,包括最基本的功能,例如标识符代表可变值。在简化并发编程中,所有函数和方法中为了保持事物不变和防止副作用都要做出巨大的改变(这些是纯函数式编程语言的基础),但当时对于主流语言的创建者来说似乎是奇怪的想法。最初的Java设计师要么没有意识到这些选择,要么认为它们太不同了,并且会劝退许多潜在的语言使用者。我们可以慷慨地说,语言设计社区当时根本没有足够的经验来理解调整在线程库中的影响。 +不幸的是,为了在更高级别的语言中获得并发性,所有语言功能都会受到影响,包括最基本的功能,例如标识符代表可变值。在简化并发编程中,所有函数和方法中为了保持事物不变和防止副作用都要做出巨大的改变(这些是纯函数式编程语言的基础),但当时对于主流语言的创建者来说似乎是奇怪的想法。最初的 Java 设计师要么没有意识到这些选择,要么认为它们太不同了,并且会劝退许多潜在的语言使用者。我们可以慷慨地说,语言设计社区当时根本没有足够的经验来理解调整在线程库中的影响。 -Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入认为Java 线程并不那么困难的陷阱。表面上看起来正常工作的程序实际上充满了微妙的并发bug。 +Java 实验告诉我们,结果是悄然灾难性的。程序员很容易陷入认为 Java 线程并不那么困难的陷阱。表面上看起来正常工作的程序实际上充满了微妙的并发 bug。 为了获得正确的并发性,语言功能必须从头开始设计并考虑并发性。木已成舟;Java 将不再是为并发而设计的语言,而只是一种允许并发的语言。 -尽管有这些基本的不可修复的缺陷,但令人印象深刻的是它已经走了这么远。Java的后续版本添加了库,以便在使用并发时提升抽象级别。事实上,我根本不会想到有可能在Java 8中进行改进:并行流和**CompletableFutures** - 这是惊人的史诗般的变化,我会惊奇地重复的查看它[^3]。 +尽管有这些基本的不可修复的缺陷,但令人印象深刻的是它已经走了这么远。Java 的后续版本添加了库,以便在使用并发时提升抽象级别。事实上,我根本不会想到有可能在 Java 8 中进行改进:并行流和 **CompletableFutures** - 这是惊人的史诗般的变化,我会惊奇地重复的查看它[^3]。 -这些改进非常有用,我们将在本章重点介绍并行流和**CompletableFutures**。虽然它们可以大大简化你对并发和后续代码的思考方式,但基本问题仍然存在:由于Java的原始设计,代码的所有部分仍然很脆弱,你仍然必须理解这些复杂和微妙的问题。Java中的线程绝不是简单或安全的;那种经历必须降级为另一种更新的语言。 +这些改进非常有用,我们将在本章重点介绍并行流和 **CompletableFutures** 。虽然它们可以大大简化你对并发和后续代码的思考方式,但基本问题仍然存在:由于 Java 的原始设计,代码的所有部分仍然很脆弱,你仍然必须理解这些复杂和微妙的问题。Java 中的线程绝不是简单或安全的;那种经历必须降级为另一种更新的语言。 ## 本章其余部分 -这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。相比于旧的替代品,使用这些会使你的生活更加轻松。但是,你仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。 +这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级 Java 并发结构。相比于旧的替代品,使用这些会使你的生活更加轻松。但是,你仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md) 包含一些更原始的 Java 并发元素的介绍。 - Parallel Streams(并行流) -到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在该语法(作为一个粉丝,我希望)会使你感到舒适,你可以获得额外的好处:你可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 +到目前为止,我已经强调了 Java 8 Streams 提供的改进语法。现在该语法(作为一个粉丝,我希望)会使你感到舒适,你可以获得额外的好处:你可以通过简单地将 parallel() 添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 -添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。 +添加 parallel() 来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相 ](#The-Brutal-Truth) 中学到的那样简单。我将演示并解释一些盲目添加 parallel() 到 Stream 表达式的缺陷。 - 创建和运行任务 -任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures更简单的机制:Executor。执行者管理一些低级Thread对象(Java中最原始的并发形式)。你创建一个任务,然后将其交给Executor去运行。 +任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或 CompletableFutures 更简单的机制:Executor。执行者管理一些低级 Thread 对象(Java 中最原始的并发形式)。你创建一个任务,然后将其交给 Executor 去运行。 -有多种类型的Executor用于不同的目的。在这里,我们将展示规范形式,代表创建和运行任务的最简单和最佳方法。 +有多种类型的 Executor 用于不同的目的。在这里,我们将展示规范形式,代表创建和运行任务的最简单和最佳方法。 - 终止长时间运行的任务 -任务独立运行,因此需要一种机制来关闭它们。典型的方法使用了一个标志,这引入了共享内存的问题,我们将使用Java的“Atomic”库来回避它。 +任务独立运行,因此需要一种机制来关闭它们。典型的方法使用了一个标志,这引入了共享内存的问题,我们将使用 Java 的“Atomic”库来回避它。 - Completable Futures -当你将衣服带到干洗店时,他们会给你一张收据。你继续完成其他任务,当你的衣服洗干净时你可以把它取走。收据是你与干洗店在后台执行的任务的连接。这是Java 5中引入的Future的方法。 +当你将衣服带到干洗店时,他们会给你一张收据。你继续完成其他任务,当你的衣服洗干净时你可以把它取走。收据是你与干洗店在后台执行的任务的连接。这是 Java 5 中引入的 Future 的方法。 -Future比以前的方法更方便,但你仍然必须出现并用收据取出干洗,如果任务没有完成你还需要等待。对于一系列操作,Futures并没有真正帮助那么多。 +Future 比以前的方法更方便,但你仍然必须出现并用收据取出干洗,如果任务没有完成你还需要等待。对于一系列操作,Futures 并没有真正帮助那么多。 -Java 8 CompletableFuture是一个更好的解决方案:它允许你将操作链接在一起,因此你不必将代码写入接口排序操作。有了CompletableFuture完美的结合,就可以更容易地做出“采购原料,组合成分,烹饪食物,提供食物,收拾餐具,储存餐具”等一系列链式操作。 +Java 8 CompletableFuture 是一个更好的解决方案:它允许你将操作链接在一起,因此你不必将代码写入接口排序操作。有了 CompletableFuture 完美的结合,就可以更容易地做出“采购原料,组合成分,烹饪食物,提供食物,收拾餐具,储存餐具”等一系列链式操作。 - 死锁 某些任务必须去**等待 - 阻塞**来获得其他任务的结果。被阻止的任务有可能等待另一个被阻止的任务,另一个被阻止的任务也在等待其他任务,等等。如果被阻止的任务链循环到第一个,没有人可以取得任何进展,你就会陷入死锁。 @@ -284,14 +284,14 @@ Java 8 CompletableFuture是一个更好的解决方案:它允许你将操作 * 努力,复杂,成本 -我们将通过模拟创建披萨的过程完成本章,首先使用并行流实现它,然后是CompletableFutures。这不仅仅是两种方法的比较,更重要的是探索你应该投入多少工作来使你的程序变得更快。 +我们将通过模拟创建披萨的过程完成本章,首先使用并行流实现它,然后是 CompletableFutures。这不仅仅是两种方法的比较,更重要的是探索你应该投入多少工作来使你的程序变得更快。 ## 并行流 -Java 8流的一个显著优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为Spliterator,它被限制为易于自动分割。我们只需要念 `.parallel()` 就会产生魔法般的结果,流中的所有内容都作为一组并行任务运行。如果你的代码是使用Streams编写的,那么并行化以提高速度似乎是一种琐事 +Java 8 流的一个显著优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为 Spliterator,它被限制为易于自动分割。我们只需要念 `.parallel()` 就会产生魔法般的结果,流中的所有内容都作为一组并行任务运行。如果你的代码是使用 Streams 编写的,那么并行化以提高速度似乎是一种琐事 -例如,考虑来自Streams的Prime.java。查找质数可能是一个耗时的过程,我们可以看到该程序的计时: +例如,考虑来自 Streams 的 Prime.java。查找质数可能是一个耗时的过程,我们可以看到该程序的计时: ```java // concurrent/ParallelPrime.java @@ -330,19 +330,19 @@ public class ParallelPrime { 1224 ``` -请注意,这不是微基准测试,因为我们计时整个程序。我们将数据保存在磁盘上以防止编译器过激的优化;如果我们没有对结果做任何事情,那么一个高级的编译器可能会观察到程序没有意义并且终止了计算(这不太可能,但并非不可能)。请注意使用nio2库编写文件的简单性(在[文件](./17-Files.md)一章中有描述)。 +请注意,这不是微基准测试,因为我们计时整个程序。我们将数据保存在磁盘上以防止编译器过激的优化;如果我们没有对结果做任何事情,那么一个高级的编译器可能会观察到程序没有意义并且终止了计算(这不太可能,但并非不可能)。请注意使用 nio2 库编写文件的简单性(在[文件 ](./17-Files.md) 一章中有描述)。 -当我注释掉[1] parallel()行时,我的结果用时大约是parallel()的三倍。 +当我注释掉[1] parallel() 行时,我的结果用时大约是 parallel() 的三倍。 -并行流似乎是一个甜蜜的交易。你所需要做的就是将编程问题转换为流,然后插入parallel()以加快速度。实际上,有时候这很容易。但遗憾的是,有许多陷阱。 +并行流似乎是一个甜蜜的交易。你所需要做的就是将编程问题转换为流,然后插入 parallel() 以加快速度。实际上,有时候这很容易。但遗憾的是,有许多陷阱。 -- parallel()不是灵丹妙药 +- parallel() 不是灵丹妙药 -作为对流和并行流的不确定性的探索,让我们看一个看似简单的问题:对增长的数字序列进行求和。事实证明有大量的方式去实现它,并且我将冒险用计时器将它们进行比较 - 我会尽量小心,但我承认我可能会在计时代码执行时遇到许多基本陷阱之一。结果可能有一些缺陷(例如JVM没有“热身”),但我认为它仍然提供了一些有用的指示。 +作为对流和并行流的不确定性的探索,让我们看一个看似简单的问题:对增长的数字序列进行求和。事实证明有大量的方式去实现它,并且我将冒险用计时器将它们进行比较 - 我会尽量小心,但我承认我可能会在计时代码执行时遇到许多基本陷阱之一。结果可能有一些缺陷(例如 JVM 没有“热身”),但我认为它仍然提供了一些有用的指示。 -我将从一个计时方法**timeTest()**开始,它采用**LongSupplier**,测量**getAsLong()**调用的长度,将结果与**checkValue**进行比较并显示结果。 +我将从一个计时方法 **timeTest()** 开始,它采用 **LongSupplier** ,测量 **getAsLong()** 调用的长度,将结果与 **checkValue** 进行比较并显示结果。 -请注意,一切都必须严格使用**long**;我花了一些时间发现隐蔽的溢出,然后才意识到在重要的地方错过了**long**。 +请注意,一切都必须严格使用 **long** ;我花了一些时间发现隐蔽的溢出,然后才意识到在重要的地方错过了 **long** 。 所有关于时间和内存的数字和讨论都是指“我的机器”。你的经历可能会有所不同。 @@ -392,21 +392,21 @@ Sum Stream Parallel: 46ms Sum Iterated: 284ms ``` -**CHECK**值是使用Carl Friedrich Gauss(高斯)在1700年代后期还在上小学的时候创建的公式计算出来的. +**CHECK** 值是使用 Carl Friedrich Gauss(高斯)在 1700 年代后期还在上小学的时候创建的公式计算出来的. - **main()** 的第一个版本使用直接生成 **Stream** 并调用 **sum()** 的方法。我们看到流的好处在于即使SZ为十亿(1_000_000_000)程序也可以很好地处理而没有溢出(为了让程序运行得快一点,我使用了较小的数字)。使用 **parallel()** 的基本范围操作明显更快。 + **main()** 的第一个版本使用直接生成 **Stream** 并调用 **sum()** 的方法。我们看到流的好处在于即使 SZ 为十亿(1_000_000_000)程序也可以很好地处理而没有溢出(为了让程序运行得快一点,我使用了较小的数字)。使用 **parallel()** 的基本范围操作明显更快。 -如果使用**iterate()**来生成序列,则减速是相当明显的,可能是因为每次生成数字时都必须调用lambda。但是如果我们尝试并行化,当**SZ**超过一百万时,结果不仅比非并行版本花费的时间更长,而且也会耗尽内存(在某些机器上)。当然,当你可以使用**range()**时,你不会使用**iterate()**,但如果你生成的东西不是简单的序列,你必须使用**iterate()**。应用**parallel()**是一个合理的尝试,但会产生令人惊讶的结果。我们将在后面的部分中探讨内存限制的原因,但我们可以对流并行算法进行初步观察: +如果使用 **iterate()** 来生成序列,则减速是相当明显的,可能是因为每次生成数字时都必须调用 lambda。但是如果我们尝试并行化,当 **SZ** 超过一百万时,结果不仅比非并行版本花费的时间更长,而且也会耗尽内存(在某些机器上)。当然,当你可以使用 **range()** 时,你不会使用 **iterate()** ,但如果你生成的东西不是简单的序列,你必须使用 **iterate()** 。应用 **parallel()** 是一个合理的尝试,但会产生令人惊讶的结果。我们将在后面的部分中探讨内存限制的原因,但我们可以对流并行算法进行初步观察: - 流并行性将输入数据分成多个部分,因此算法可以应用于那些单独的部分。 - 数组分割成本低,分割均匀且对分割的大小有着完美的掌控。 - 链表没有这些属性;“拆分”一个链表仅仅意味着把它分成“第一元素”和“其余元素”,这相对无用。 -- 无状态生成器的行为类似于数组;上面使用的 **range()** 就是无状态的。 -- 迭代生成器的行为类似于链表; **iterate()** 是一个迭代生成器。 +- 无状态生成器的行为类似于数组;上面使用的 **range()** 就是无状态的。 +- 迭代生成器的行为类似于链表; **iterate()** 是一个迭代生成器。 现在让我们尝试通过在数组中填充值并对数组求和来解决问题。因为数组只分配了一次,所以我们不太可能遇到垃圾收集时序问题。 -首先我们将尝试一个充满原始**long**的数组: +首先我们将尝试一个充满原始 **long** 的数组: ```java // concurrent/Summing2.java @@ -451,9 +451,9 @@ Basic Sum: 106ms parallelPrefix: 265ms ``` -第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。 +第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。 -最后,考虑使用包装类**Long**的效果: +最后,考虑使用包装类 **Long** 的效果: ```java // concurrent/Summing3.java @@ -499,9 +499,9 @@ Sum: 21ms Long parallelPrefix: 3616ms ``` -现在可用的内存量大约减半,并且所有情况下所需的时间都会很长,除了**basicSum()**,它只是循环遍历数组。令人惊讶的是, **Arrays.parallelPrefix()** 比任何其他方法都要花费更长的时间。 +现在可用的内存量大约减半,并且所有情况下所需的时间都会很长,除了 **basicSum()** ,它只是循环遍历数组。令人惊讶的是, **Arrays.parallelPrefix()** 比任何其他方法都要花费更长的时间。 -我将 **parallel()** 版本分开了,因为在上面的程序中运行它导致了一个冗长的垃圾收集,扭曲了结果: +我将 **parallel()** 版本分开了,因为在上面的程序中运行它导致了一个冗长的垃圾收集,扭曲了结果: ```java // concurrent/Summing4.java @@ -528,15 +528,15 @@ public class Summing4 { Long Parallel: 1014ms ``` -它比非parallel()版本略快,但并不显着。 +它比非 parallel() 版本略快,但并不显着。 -导致时间增加的一个重要原因是处理器内存缓存。使用**Summing2.java**中的原始**long**,数组**la**是连续的内存。处理器可以更容易地预测该阵列的使用,并使缓存充满下一个需要的阵列元素。访问缓存比访问主内存快得多。似乎 **Long parallelPrefix** 计算受到影响,因为它为每个计算读取两个数组元素,并将结果写回到数组中,并且每个都为**Long**生成一个超出缓存的引用。 +导致时间增加的一个重要原因是处理器内存缓存。使用 **Summing2.java** 中的原始 **long** ,数组 **la** 是连续的内存。处理器可以更容易地预测该阵列的使用,并使缓存充满下一个需要的阵列元素。访问缓存比访问主内存快得多。似乎 **Long parallelPrefix** 计算受到影响,因为它为每个计算读取两个数组元素,并将结果写回到数组中,并且每个都为 **Long** 生成一个超出缓存的引用。 -使用**Summing3.java**和**Summing4.java**,**aL**是一个**Long**数组,它不是一个连续的数据数组,而是一个连续的**Long**对象引用数组。尽管该数组可能会在缓存中出现,但指向的对象几乎总是不在缓存中。 +使用 **Summing3.java** 和 **Summing4.java** ,**aL** 是一个 **Long** 数组,它不是一个连续的数据数组,而是一个连续的 **Long** 对象引用数组。尽管该数组可能会在缓存中出现,但指向的对象几乎总是不在缓存中。 -这些示例使用不同的SZ值来显示内存限制。 +这些示例使用不同的 SZ 值来显示内存限制。 -为了进行时间比较,以下是SZ设置为最小值1000万的结果: +为了进行时间比较,以下是 SZ 设置为最小值 1000 万的结果: **Sum Stream: 69msSum Stream Parallel: 18msSum @@ -548,15 +548,15 @@ parallelPrefix: 28ms Long Array Stream Reduce: 1046ms Long Basic Sum: 21ms Long parallelPrefix: 3287ms -Long Parallel: 1008ms** +Long Parallel: 1008ms** -虽然Java 8的各种内置“并行”工具非常棒,但我认为它们被视为神奇的灵丹妙药:“只需添加parallel()并且它会更快!” 我希望我已经开始表明情况并非所有都是如此,并且盲目地应用内置的“并行”操作有时甚至会使运行速度明显变慢。 +虽然 Java 8 的各种内置“并行”工具非常棒,但我认为它们被视为神奇的灵丹妙药:“只需添加 parallel() 并且它会更快!” 我希望我已经开始表明情况并非所有都是如此,并且盲目地应用内置的“并行”操作有时甚至会使运行速度明显变慢。 -- parallel()/limit()交点 +- parallel()/limit() 交点 -使用**parallel()**时会有更复杂的问题。从其他语言中吸取的流机制被设计为大约是一个无限的流模型。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。 +使用 **parallel()** 时会有更复杂的问题。从其他语言中吸取的流机制被设计为大约是一个无限的流模型。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。 -Java 8将两者合并起来。例如,**Collections**没有内置的**map()**操作。在**Collection**和**Map**中唯一类似流的批处理操作是**forEach()**。如果要执行**map()**和**reduce()**等操作,必须首先将**Collection**转换为存在这些操作的**Stream**: +Java 8 将两者合并起来。例如,**Collections** 没有内置的 **map()** 操作。在 **Collection** 和 **Map** 中唯一类似流的批处理操作是 **forEach()** 。如果要执行 **map()** 和 **reduce()** 等操作,必须首先将 **Collection** 转换为存在这些操作的 **Stream** : ```java // concurrent/CollectionIntoStream.java @@ -595,9 +595,9 @@ bynxt :PENCUXGVGINNLOZVEWPPCPOALJLNXT ``` -**Collection**确实有一些批处理操作,如**removeAll()**,**removeIf()**和**retainAll()**,但这些都是破坏性的操作。**ConcurrentHashMap**对**forEach**和**reduce**操作有特别广泛的支持。 +**Collection** 确实有一些批处理操作,如 **removeAll()** ,**removeIf()** 和 **retainAll()** ,但这些都是破坏性的操作。**ConcurrentHashMap** 对 **forEach** 和 **reduce** 操作有特别广泛的支持。 -在许多情况下,只在集合上调用**stream()**或者**parallelStream()**没有问题。但是,有时将**Stream**与**Collection**混合会产生意想不到的结果。这是一个有趣的难题: +在许多情况下,只在集合上调用 **stream()** 或者 **parallelStream()** 没有问题。但是,有时将 **Stream** 与 **Collection** 混合会产生意想不到的结果。这是一个有趣的难题: ```java // concurrent/ParallelStreamPuzzle.java @@ -626,13 +626,13 @@ public class ParallelStreamPuzzle { */ ``` -如果[1]注释运行它,它会产生预期的: -**[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]** -每次。但是包含了parallel(),它看起来像一个随机数生成器,带有输出(从一次运行到下一次运行不同),如: -**[0, 3, 6, 8, 11, 14, 17, 20, 23, 26]** +如果[1] 注释运行它,它会产生预期的: +**[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]** +每次。但是包含了 parallel(),它看起来像一个随机数生成器,带有输出(从一次运行到下一次运行不同),如: +**[0, 3, 6, 8, 11, 14, 17, 20, 23, 26]** 这样一个简单的程序怎么会如此糟糕呢?让我们考虑一下我们在这里要实现的目标:“并行生成。”那意味着什么?一堆线程都在从一个生成器取值,然后以某种方式选择有限的结果集?代码看起来很简单,但它变成了一个特别棘手的问题。 -为了看到它,我们将添加一些仪器。由于我们正在处理线程,因此我们必须将任何跟踪信息捕获到并发数据结构中。在这里我使用**ConcurrentLinkedDeque**: +为了看到它,我们将添加一些仪器。由于我们正在处理线程,因此我们必须将任何跟踪信息捕获到并发数据结构中。在这里我使用 **ConcurrentLinkedDeque** : ```java // concurrent/ParallelStreamPuzzle2.java @@ -672,11 +672,12 @@ public class ParallelStreamPuzzle2 { [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ``` -current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞争条件;**parallel()**允许多个线程调用**get()**。 +current 是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞争条件;**parallel()** 允许多个线程调用 **get()** 。 -在查看 **PSP2.txt**.**IntGenerator.get()** 被调用1024次时,你可能会感到惊讶。 +在查看 **PSP2.txt** .**IntGenerator.get()** 被调用 1024 次时,你可能会感到惊讶。 -**0: main +``` +0: main 1: ForkJoinPool.commonPool-worker-1 2: ForkJoinPool.commonPool-worker-2 3: ForkJoinPool.commonPool-worker-2 @@ -698,13 +699,14 @@ current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞 20: ForkJoinPool.commonPool-worker-110 21: ForkJoinPool.commonPool-worker-110 22: ForkJoinPool.commonPool-worker-110 -23: ForkJoinPool.commonPool-worker-1** +23: ForkJoinPool.commonPool-worker-1 +``` 这个块大小似乎是内部实现的一部分(尝试使用`limit()` 的不同参数来查看不同的块大小)。将`parallel()`与`limit()`结合使用可以预取一串值,作为流输出。 -试着想象一下这里发生了什么:一个流抽象出无限序列,按需生成。当你要求它并行产生流时,你要求所有这些线程尽可能地调用`get()`。添加`limit()`,你说“只需要这些。”基本上,当你为了随机输出而选择将`parallel()`与`limit()`结合使用时,这种方法可能对你正在解决的问题有效。但是当你这样做时,你必须明白。这是一个仅限专家的功能,而不是要争辩说“Java弄错了”。 +试着想象一下这里发生了什么:一个流抽象出无限序列,按需生成。当你要求它并行产生流时,你要求所有这些线程尽可能地调用`get()`。添加`limit()`,你说“只需要这些。”基本上,当你为了随机输出而选择将`parallel()`与`limit()`结合使用时,这种方法可能对你正在解决的问题有效。但是当你这样做时,你必须明白。这是一个仅限专家的功能,而不是要争辩说“Java 弄错了”。 -什么是更合理的方法来解决问题?好吧,如果你想生成一个int流,你可以使用IntStream.range(),如下所示: +什么是更合理的方法来解决问题?好吧,如果你想生成一个 int 流,你可以使用 IntStream.range(),如下所示: ```java // concurrent/ParallelStreamPuzzle3.java @@ -741,31 +743,31 @@ public class ParallelStreamPuzzle3 { [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ``` -为了表明**parallel()**确实有效,我添加了一个对**peek()**的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。 +为了表明 **parallel()** 确实有效,我添加了一个对 **peek()** 的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。 -你还可以看到**boxed()**的添加,它接受**int**流并将其转换为**Integer**流。 +你还可以看到 **boxed()** 的添加,它接受 **int** 流并将其转换为 **Integer** 流。 -现在我们得到多个线程产生不同的值,但它只产生10个请求的值,而不是1024个产生10个值。 +现在我们得到多个线程产生不同的值,但它只产生 10 个请求的值,而不是 1024 个产生 10 个值。 -它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。很难想象什么时候用并行生成一个简单的数字序列会有意义。如果你要生成的东西需要很高的成本,它可能有意义 - 但这都是猜测。只有通过测试我们才能知道用并行是否有效。记住这句格言:“首先使它工作,然后使它更快地工作 - 只有当你必须这样做时。”**parallel()**和**limit()**仅供专家使用(把话说在前面,我不认为自己是这里的专家)。 +它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。很难想象什么时候用并行生成一个简单的数字序列会有意义。如果你要生成的东西需要很高的成本,它可能有意义 - 但这都是猜测。只有通过测试我们才能知道用并行是否有效。记住这句格言:“首先使它工作,然后使它更快地工作 - 只有当你必须这样做时。”将 **parallel()** 和 **limit()** 结合使用仅供专家操作(把话说在前面,我不认为自己是这里的专家)。 - 并行流只看起来很容易 -实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,仅仅将**parallel()**加到你的Stream操作上并不一定是安全的事情。在使用**parallel()**之前,你必须了解并行性如何帮助或损害你的操作。一个基本认知错误就是认为使用并行性总是一个好主意。事实上并不是。Stream意味着你不需要重写所有代码便可以并行运行它。但是流的出现并不意味着你可以不用理解并行的原理以及不用考虑并行是否真的有助于实现你的目标。 +实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,仅仅将 **parallel()** 加到你的 Stream 操作上并不一定是安全的事情。在使用 **parallel()** 之前,你必须了解并行性如何帮助或损害你的操作。一个基本认知错误就是认为使用并行性总是一个好主意。事实上并不是。Stream 意味着你不需要重写所有代码便可以并行运行它。但是流的出现并不意味着你可以不用理解并行的原理以及不用考虑并行是否真的有助于实现你的目标。 ## 创建和运行任务 -如果无法通过并行流实现并发,则必须创建并运行自己的任务。稍后你将看到运行任务的理想Java 8方法是CompletableFuture,但我们将使用更基本的工具介绍概念。 +如果无法通过并行流实现并发,则必须创建并运行自己的任务。稍后你将看到运行任务的理想 Java 8 方法是 CompletableFuture,但我们将使用更基本的工具介绍概念。 -Java并发的历史始于非常原始和有问题的机制,并且充满了各种尝试的改进。这些主要归入附录:[低级并发(Appendix: Low-Level Concurrency)](./Appendix-Low-Level-Concurrency.md)。在这里,我们将展示一个规范形式,表示创建和运行任务的最简单,最好的方法。与并发中的所有内容一样,存在各种变体,但这些变体要么降级到该附录,要么超出本书的范围。 +Java 并发的历史始于非常原始和有问题的机制,并且充满了各种尝试的改进。这些主要归入附录:[低级并发 (Appendix: Low-Level Concurrency)](./Appendix-Low-Level-Concurrency.md)。在这里,我们将展示一个规范形式,表示创建和运行任务的最简单,最好的方法。与并发中的所有内容一样,存在各种变体,但这些变体要么降级到该附录,要么超出本书的范围。 - Tasks and Executors -在Java的早期版本中,你通过直接创建自己的Thread对象来使用线程,甚至将它们子类化以创建你自己的特定“任务线程”对象。你手动调用了构造函数并自己启动了线程。 +在 Java 的早期版本中,你通过直接创建自己的 Thread 对象来使用线程,甚至将它们子类化以创建你自己的特定“任务线程”对象。你手动调用了构造函数并自己启动了线程。 -创建所有这些线程的开销变得非常重要,现在不鼓励采用手动操作方法。在Java 5中,添加了类来为你处理线程池。你可以将任务创建为单独的类型,然后将其交给ExecutorService以运行该任务,而不是为每种不同类型的任务创建新的Thread子类型。ExecutorService为你管理线程,并且在运行任务后重新循环线程而不是丢弃线程。 +创建所有这些线程的开销变得非常重要,现在不鼓励采用手动操作方法。在 Java 5 中,添加了类来为你处理线程池。你可以将任务创建为单独的类型,然后将其交给 ExecutorService 以运行该任务,而不是为每种不同类型的任务创建新的 Thread 子类型。ExecutorService 为你管理线程,并且在运行任务后重新循环线程而不是丢弃线程。 -首先,我们将创建一个几乎不执行任务的任务。它“sleep”(暂停执行)100毫秒,显示其标识符和正在执行任务的线程的名称,然后完成: +首先,我们将创建一个几乎不执行任务的任务。它“sleep”(暂停执行)100 毫秒,显示其标识符和正在执行任务的线程的名称,然后完成: ```java // concurrent/NapTask.java @@ -788,7 +790,7 @@ public class NapTask implements Runnable { } ``` -这只是一个**Runnable**:一个包含**run()**方法的类。它没有包含实际运行任务的机制。我们使用**Nap**类中的“sleep”: +这只是一个 **Runnable** :一个包含 **run()** 方法的类。它没有包含实际运行任务的机制。我们使用 **Nap** 类中的“sleep”: ```java // onjava/Nap.java @@ -810,11 +812,11 @@ public class Nap { ``` 为了消除异常处理的视觉干扰,这被定义为实用程序。第二个构造函数在超时时显示一条消息 -对**TimeUnit.MILLISECONDS.sleep()**的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()**是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。 +对 **TimeUnit.MILLISECONDS.sleep()** 的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS 任务管理器定期检查 **sleep()** 是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。 -你可以看到**sleep()**抛出一个受检的**InterruptedException**;这是原始Java设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。 +你可以看到 **sleep()** 抛出一个受检的 **InterruptedException** ;这是原始 Java 设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。 -要执行任务,我们将从最简单的方法--SingleThreadExecutor开始: +要执行任务,我们将从最简单的方法--SingleThreadExecutor 开始: ```java //concurrent/SingleThreadExecutor.java @@ -867,13 +869,13 @@ main awaiting termination NapTask[9] pool-1-thread-1 ``` -首先请注意,没有**SingleThreadExecutor**类。**newSingleThreadExecutor()**是**Executors**中的工厂,它创建特定类型的[^4] +首先请注意,没有 **SingleThreadExecutor** 类。**newSingleThreadExecutor()** 是 **Executors** 中的一个工厂方法,它创建特定类型的 **ExecutorService** [^4] -我创建了十个NapTasks并将它们提交给ExecutorService,这意味着它们开始自己运行。然而,在此期间,main()继续做事。当我运行callexec.shutdown()时,它告诉ExecutorService完成已经提交的任务,但不接受任何新任务。此时,这些任务仍然在运行,因此我们必须等到它们在退出main()之前完成。这是通过检查exec.isTerminated()来实现的,这在所有任务完成后变为true。 +我创建了十个 NapTasks 并将它们提交给 ExecutorService,这意味着它们开始自己运行。然而,在此期间,main() 继续做事。当我运行 callexec.shutdown() 时,它告诉 ExecutorService 完成已经提交的任务,但不接受任何新任务。此时,这些任务仍然在运行,因此我们必须等到它们在退出 main() 之前完成。这是通过检查 exec.isTerminated() 来实现的,这在所有任务完成后变为 true。 -请注意,main()中线程的名称是main,并且只有一个其他线程pool-1-thread-1。此外,交错输出显示两个线程确实同时运行。 +请注意,main() 中线程的名称是 main,并且只有一个其他线程 pool-1-thread-1。此外,交错输出显示两个线程确实同时运行。 -如果你只是调用exec.shutdown(),程序将完成所有任务。也就是说,不需要**while(!exec.isTerminated())**。 +如果你只是调用 exec.shutdown(),程序将完成所有任务。也就是说,不需要 **while(!exec.isTerminated())** 。 ```java // concurrent/SingleThreadExecutor2.java @@ -906,7 +908,7 @@ NapTask[8] pool-1-thread-1 NapTask[9] pool-1-thread-1 ``` -一旦你callexec.shutdown(),尝试提交新任务将抛出RejectedExecutionException。 +一旦你 callexec.shutdown(),尝试提交新任务将抛出 RejectedExecutionException。 ```java // concurrent/MoreTasksAfterShutdown.java @@ -932,11 +934,11 @@ public class MoreTasksAfterShutdown { java.util.concurrent.RejectedExecutionException: TaskNapTask[99] rejected from java.util.concurrent.ThreadPoolExecutor@4e25154f[Shutting down, pool size = 1,active threads = 1, queued tasks = 0, completed tasks =0]NapTask[1] pool-1-thread-1 ``` -**exec.shutdown()**的替代方法是**exec.shutdownNow()**,它除了不接受新任务外,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错并且不鼓励。 +**exec.shutdown()** 的替代方法是 **exec.shutdownNow()** ,它除了不接受新任务外,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错并且不鼓励。 - 使用更多线程 -使用线程的重点是(几乎总是)更快地完成任务,那么我们为什么要限制自己使用SingleThreadExecutor呢?查看执行**Executors**的Javadoc,你将看到更多选项。例如CachedThreadPool: +使用线程的重点是(几乎总是)更快地完成任务,那么我们为什么要限制自己使用 SingleThreadExecutor 呢?查看执行 **Executors** 的 Javadoc,你将看到更多选项。例如 CachedThreadPool: ```java // concurrent/CachedThreadPool.java @@ -969,7 +971,7 @@ NapTask[6] pool-1-thread-7 NapTask[5] pool-1-thread-6 ``` -当你运行这个程序时,你会发现它完成得更快。这是有道理的,每个任务都有自己的线程,所以它们都并行运行,而不是使用相同的线程来顺序运行每个任务。这似乎没毛病,很难理解为什么有人会使用SingleThreadExecutor。 +当你运行这个程序时,你会发现它完成得更快。这是有道理的,每个任务都有自己的线程,所以它们都并行运行,而不是使用相同的线程来顺序运行每个任务。这似乎没毛病,很难理解为什么有人会使用 SingleThreadExecutor。 要理解这个问题,我们需要一个更复杂的任务: @@ -992,7 +994,7 @@ public class InterferingTask implements Runnable { ``` -每个任务增加val一百次。这似乎很简单。让我们用CachedThreadPool尝试一下: +每个任务增加 val 一百次。这似乎很简单。让我们用 CachedThreadPool 尝试一下: ```java // concurrent/CachedThreadPool2.java @@ -1025,7 +1027,7 @@ public class CachedThreadPool2 { 6 pool-1-thread-7 1000 ``` -输出不是我们所期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入val的单个实例,并且他们正在踩着彼此的脚趾。我们称这样的类是线程不安全的。让我们看看SingleThreadExecutor会发生什么: +输出不是我们所期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入 val 的单个实例,并且他们正在踩着彼此的脚趾。我们称这样的类是线程不安全的。让我们看看 SingleThreadExecutor 会发生什么: ```java // concurrent/SingleThreadExecutor3.java @@ -1058,15 +1060,15 @@ public class SingleThreadExecutor3 { 9 pool-1-thread-1 1000 ``` -现在我们每次都得到一致的结果,尽管**InterferingTask**缺乏线程安全性。这是SingleThreadExecutor的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,因此强加了线程安全性。这种现象称为线程封闭,因为在单线程上运行任务限制了它们的影响。线程封闭限制了加速,但可以节省很多困难的调试和重写。 +现在我们每次都得到一致的结果,尽管 **InterferingTask** 缺乏线程安全性。这是 SingleThreadExecutor 的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,因此强加了线程安全性。这种现象称为线程封闭,因为在单线程上运行任务限制了它们的影响。线程封闭限制了加速,但可以节省很多困难的调试和重写。 - 产生结果 -因为**InterferingTask**是一个**Runnable**,它没有返回值,因此只能使用副作用产生结果 - 操纵缓冲值而不是返回结果。副作用是并发编程中的主要问题之一,因为我们看到了**CachedThreadPool2.java**。**InterferingTask**中的**val**被称为可变共享状态,这就是问题所在:多个任务同时修改同一个变量会产生竞争。结果取决于首先在终点线上执行哪个任务,并修改变量(以及其他可能性的各种变化)。 +因为 **InterferingTask** 是一个 **Runnable** ,它没有返回值,因此只能使用副作用产生结果 - 操纵缓冲值而不是返回结果。副作用是并发编程中的主要问题之一,因为我们看到了 **CachedThreadPool2.java** 。**InterferingTask** 中的 **val** 被称为可变共享状态,这就是问题所在:多个任务同时修改同一个变量会产生竞争。结果取决于首先在终点线上执行哪个任务,并修改变量(以及其他可能性的各种变化)。 避免竞争条件的最好方法是避免可变的共享状态。我们可以称之为自私的孩子原则:什么都不分享。 -使用**InterferingTask**,最好删除副作用并返回任务结果。为此,我们创建**Callable**而不是**Runnable**: +使用 **InterferingTask** ,最好删除副作用并返回任务结果。为此,我们创建 **Callable** 而不是 **Runnable** : ```java // concurrent/CountingTask.java @@ -1087,9 +1089,9 @@ public class CountingTask implements Callable { ``` -**call()完全独立于所有其他CountingTasks生成其结果**,这意味着没有可变的共享状态 +**call() 完全独立于所有其他 CountingTasks 生成其结果**,这意味着没有可变的共享状态 -**ExecutorService**允许你使用**invokeAll()**启动集合中的每个Callable: +**ExecutorService** 允许你使用 **invokeAll()** 启动集合中的每个 Callable: ```java // concurrent/CachedThreadPool3.java @@ -1139,7 +1141,7 @@ sum = 1000 ``` -只有在所有任务完成后,**invokeAll()**才会返回一个**Future**列表,每个任务一个**Future**。**Future**是Java 5中引入的机制,允许你提交任务而无需等待它完成。在这里,我们使用**ExecutorService.submit()**: +只有在所有任务完成后,**invokeAll()** 才会返回一个 **Future** 列表,每个任务一个 **Future** 。**Future** 是 Java 5 中引入的机制,允许你提交任务而无需等待它完成。在这里,我们使用 **ExecutorService.submit()** : ```java // concurrent/Futures.java @@ -1165,15 +1167,15 @@ public class Futures { 100 ``` -- [1] 当你的任务在尚未完成的**Future**上调用**get()**时,调用会阻塞(等待)直到结果可用。 +- [1] 当你的任务在尚未完成的 **Future** 上调用 **get()** 时,调用会阻塞(等待)直到结果可用。 -但这意味着,在**CachedThreadPool3.java**中,**Future**似乎是多余的,因为**invokeAll()**甚至在所有任务完成之前都不会返回。但是,这里的Future并不用于延迟结果,而是用于捕获任何可能发生的异常。 +但这意味着,在 **CachedThreadPool3.java** 中,**Future** 似乎是多余的,因为 **invokeAll()** 甚至在所有任务完成之前都不会返回。但是,这里的 Future 并不用于延迟结果,而是用于捕获任何可能发生的异常。 -还要注意在**CachedThreadPool3.java.get()**中抛出异常,因此**extractResult()**在Stream中执行此提取。 +还要注意在 **CachedThreadPool3.java.get()** 中抛出异常,因此 **extractResult()** 在 Stream 中执行此提取。 -因为当你调用**get()**时,**Future**会阻塞,所以它只能解决等待任务完成才暴露问题。最终,**Futures**被认为是一种无效的解决方案,现在不鼓励,我们推荐Java 8的**CompletableFuture**,这将在本章后面探讨。当然,你仍会在遗留库中遇到Futures。 +因为当你调用 **get()** 时,**Future** 会阻塞,所以它只能解决等待任务完成才暴露问题。最终,**Futures** 被认为是一种无效的解决方案,现在不鼓励,我们推荐 Java 8 的 **CompletableFuture** ,这将在本章后面探讨。当然,你仍会在遗留库中遇到 Futures。 -我们可以使用并行Stream以更简单,更优雅的方式解决这个问题: +我们可以使用并行 Stream 以更简单,更优雅的方式解决这个问题: ```java // concurrent/CountingStream.java @@ -1211,9 +1213,9 @@ public class CountingStream { 这不仅更容易理解,而且我们需要做的就是将 `parallel()` 插入到其他顺序操作中,然后一切都在同时运行。 -- Lambda和方法引用作为任务 +- Lambda 和方法引用作为任务 -在 **java8** 有了 **lambdas** 和方法引用,你不需要受限于只能使用 **Runnable** 和 **Callable** 。因为 java8 的**lambdas** 和方法引用可以通过匹配方法签名来使用(即,它支持结构一致性),所以我们可以将非 **Runnable** 或 **Callable** 的参数传递给 `ExecutorService` : +在 **java8** 有了 **lambdas** 和方法引用,你不需要受限于只能使用 **Runnable** 和 **Callable** 。因为 java8 的 **lambdas** 和方法引用可以通过匹配方法签名来使用(即,它支持结构一致性),所以我们可以将非 **Runnable** 或 **Callable** 的参数传递给 `ExecutorService` : ```java // concurrent/LambdasAndMethodReferences.java @@ -1255,24 +1257,24 @@ Lambda2 ``` -这里,前两个**submit()**调用可以改为调用**execute()**。所有**submit()**调用都返回**Futures**,你可以在后两次调用的情况下提取结果。 +这里,前两个 **submit()** 调用可以改为调用 **execute()** 。所有 **submit()** 调用都返回 **Futures** ,你可以在后两次调用的情况下提取结果。 ## 终止耗时任务 -并发程序通常使用长时间运行的任务。可调用任务在完成时返回值;虽然这给它一个有限的寿命,但仍然可能很长。可运行的任务有时被设置为永远运行的后台进程。你经常需要一种方法在正常完成之前停止**Runnable**和**Callable**任务,例如当你关闭程序时。 +并发程序通常使用长时间运行的任务。可调用任务在完成时返回值;虽然这给它一个有限的寿命,但仍然可能很长。可运行的任务有时被设置为永远运行的后台进程。你经常需要一种方法在正常完成之前停止 **Runnable** 和 **Callable** 任务,例如当你关闭程序时。 -最初的Java设计提供了中断运行任务的机制(为了向后兼容,仍然存在);中断机制包括阻塞问题。中断任务既乱又复杂,因为你必须了解可能发生中断的所有可能状态,以及可能导致的数据丢失。使用中断被视为反对模式,但我们仍然被迫接受。 +最初的 Java 设计提供了中断运行任务的机制(为了向后兼容,仍然存在);中断机制包括阻塞问题。中断任务既乱又复杂,因为你必须了解可能发生中断的所有可能状态,以及可能导致的数据丢失。使用中断被视为反对模式,但我们仍然被迫接受。 InterruptedException,因为设计的向后兼容性残留。 -任务终止的最佳方法是设置任务周期性检查的标志。然后任务可以通过自己的shutdown进程并正常终止。不是在任务中随机关闭线程,而是要求任务在到达了一个较好时自行终止。这总是产生比中断更好的结果,以及更容易理解的更合理的代码。 +任务终止的最佳方法是设置任务周期性检查的标志。然后任务可以通过自己的 shutdown 进程并正常终止。不是在任务中随机关闭线程,而是要求任务在到达了一个较好时自行终止。这总是产生比中断更好的结果,以及更容易理解的更合理的代码。 -以这种方式终止任务听起来很简单:设置任务可以看到的**boolean** flag。编写任务,以便定期检查标志并执行正常终止。这实际上就是你所做的,但是有一个复杂的问题:我们的旧克星,共同的可变状态。如果该标志可以被另一个任务操纵,则存在碰撞可能性。 +以这种方式终止任务听起来很简单:设置任务可以看到的 **boolean** flag。编写任务,以便定期检查标志并执行正常终止。这实际上就是你所做的,但是有一个复杂的问题:我们的旧克星,共同的可变状态。如果该标志可以被另一个任务操纵,则存在碰撞可能性。 -在研究Java文献时,你会发现很多解决这个问题的方法,经常使用**volatile**关键字。我们将使用更简单的技术并避免所有易变的参数,这些都在[附录:低级并发](./Appendix-Low-Level-Concurrency.md)中有所涉及。 +在研究 Java 文献时,你会发现很多解决这个问题的方法,经常使用 **volatile** 关键字。我们将使用更简单的技术并避免所有易变的参数,这些都在[附录:低级并发 ](./Appendix-Low-Level-Concurrency.md) 中有所涉及。 -Java 5引入了**Atomic**类,它提供了一组可以使用的类型,而不必担心并发问题。我们将添加**AtomicBoolean**标志,告诉任务清理自己并退出。 +Java 5 引入了 **Atomic** 类,它提供了一组可以使用的类型,而不必担心并发问题。我们将添加 **AtomicBoolean** 标志,告诉任务清理自己并退出。 ```java // concurrent/QuittableTask.java @@ -1297,14 +1299,14 @@ public class QuittableTask implements Runnable { ``` -虽然多个任务可以在同一个实例上成功调用**quit()**,但是**AtomicBoolean**可以防止多个任务同时实际修改**running**,从而使**quit()**方法成为线程安全的。 +虽然多个任务可以在同一个实例上成功调用 **quit()** ,但是 **AtomicBoolean** 可以防止多个任务同时实际修改 **running** ,从而使 **quit()** 方法成为线程安全的。 -- [1]:只要运行标志为true,此任务的run()方法将继续。 +- [1]:只要运行标志为 true,此任务的 run() 方法将继续。 - [2]: 显示仅在任务退出时发生。 -需要**running AtomicBoolean**证明编写Java program并发时最基本的困难之一是,如果**running**是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,你没有任何反馈来告诉你已经做错了。通常,你编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。 +需要 **running AtomicBoolean** 证明编写 Java program 并发时最基本的困难之一是,如果 **running** 是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,你没有任何反馈来告诉你已经做错了。通常,你编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。 -作为测试,我们将启动很多QuittableTasks然后关闭它们。尝试使用较大的COUNT值 +作为测试,我们将启动很多 QuittableTasks 然后关闭它们。尝试使用较大的 COUNT 值 ```java // concurrent/QuittingTasks.java @@ -1335,11 +1337,11 @@ public class QuittingTasks { 136 131 135 139 148 140 2 126 6 5 1 18 129 17 14 13 2122 9 10 30 33 58 37 125 26 34 133 145 78 137 141 138 6274 142 86 65 73 146 70 42 149 121 110 134 105 82 117106 113 122 45 114 118 38 50 29 90 101 89 57 53 94 4161 66 130 69 77 81 85 93 25 102 54 109 98 49 46 97 ``` -我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。 +我使用 **peek()** 将 **QuittableTasks** 传递给 **ExecutorService** ,然后将这些任务收集到 **List.main()** 中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用 quit() 方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。 -## CompletableFuture类 +## CompletableFuture 类 -作为介绍,这里是使用CompletableFutures在QuittingTasks.java中: +作为介绍,这里是使用 CompletableFutures 在 QuittingTasks.java 中: ```java // concurrent/QuittingCompletable.java @@ -1378,7 +1380,7 @@ public class QuittingCompletable { ### 基本用法 -这是一个带有静态方法**work()**的类,它对该类的对象执行某些工作: +这是一个带有静态方法 **work()** 的类,它对该类的对象执行某些工作: ```java // concurrent/Machina.java @@ -1413,9 +1415,9 @@ public class Machina { ``` -这是一个有限状态机,一个微不足道的机器,因为它没有分支......它只是从头到尾遍历一条路径。**work()**方法将机器从一个状态移动到下一个状态,并且需要100毫秒才能完成“工作”。 +这是一个有限状态机,一个微不足道的机器,因为它没有分支......它只是从头到尾遍历一条路径。**work()** 方法将机器从一个状态移动到下一个状态,并且需要 100 毫秒才能完成“工作”。 -**CompletableFuture**可以被用来做的一件事是, 使用**completedFuture()**将它感兴趣的对象进行包装。 +**CompletableFuture** 可以被用来做的一件事是, 使用 **completedFuture()** 将它感兴趣的对象进行包装。 ```java // concurrent/CompletedMachina.java @@ -1435,11 +1437,11 @@ public class CompletedMachina { } ``` -**completedFuture()**创建一个“已经完成”的**CompletableFuture**。对这样一个未来做的唯一有用的事情是**get()**里面的对象,所以这看起来似乎没有用。注意**CompletableFuture**被输入到它包含的对象。这个很重要。 +**completedFuture()** 创建一个“已经完成”的 **CompletableFuture** 。对这样一个未来做的唯一有用的事情是 **get()** 里面的对象,所以这看起来似乎没有用。注意 **CompletableFuture** 被输入到它包含的对象。这个很重要。 -通常,**get()**在等待结果时阻塞调用线程。此块可以通过**InterruptedException**或**ExecutionException**中断。在这种情况下,阻止永远不会发生,因为CompletableFutureis已经完成,所以答案立即可用。 +通常,**get()** 在等待结果时阻塞调用线程。此块可以通过 **InterruptedException** 或 **ExecutionException** 中断。在这种情况下,阻止永远不会发生,因为 **CompletableFuture** 已经完成,所以结果立即可用。 -当我们将**handle()**包装在**CompletableFuture**中时,发现我们可以在**CompletableFuture**上添加操作来处理所包含的对象,使得事情变得更加有趣: +当我们将 **handle()** 包装在 **CompletableFuture** 中时,发现我们可以在 **CompletableFuture** 上添加操作来处理所包含的对象,使得事情变得更加有趣: ```java // concurrent/CompletableApply.java @@ -1470,11 +1472,11 @@ Machina0: THREE Machina0: complete ``` -`thenApply()` 应用一个接收输入并产生输出的函数。在本例中,`work()` 函数产生的类型与它所接收的类型相同 (`Machina`),因此每个 `CompletableFuture`添加的操作的返回类型都为 `Machina`,但是(类似于流中的 `map()` )函数也可以返回不同的类型,这将体现在返回类型上。 +`thenApply()` 应用一个接收输入并产生输出的函数。在本例中,`work()` 函数产生的类型与它所接收的类型相同 (`Machina`),因此每个 `CompletableFuture`添加的操作的返回类型都为 `Machina`,但是 (类似于流中的 `map()` ) 函数也可以返回不同的类型,这将体现在返回类型上。 -你可以在此处看到有关**CompletableFutures**的重要信息:它们会在你执行操作时自动解包并重新包装它们所携带的对象。这使得编写和理解代码变得更加简单, 而不会在陷入在麻烦的细节中。 +你可以在此处看到有关 **CompletableFutures** 的重要信息:它们会在你执行操作时自动解包并重新包装它们所携带的对象。这使得编写和理解代码变得更加简单, 而不会在陷入在麻烦的细节中。 -我们可以消除中间变量并将操作链接在一起,就像我们使用Streams一样: +我们可以消除中间变量并将操作链接在一起,就像我们使用 Streams 一样: ```java // concurrent/CompletableApplyChained.javaimport java.util.concurrent.*; @@ -1505,9 +1507,9 @@ Machina0: complete ``` 这里我们还添加了一个 `Timer`,它的功能在每一步都显性地增加 100ms 等待时间之外,还将 `CompletableFuture` 内部 `thenApply` 带来的额外开销给体现出来了。 -**CompletableFutures** 的一个重要好处是它们鼓励使用私有子类原则(不共享任何东西)。默认情况下,使用 **thenApply()** 来应用一个不对外通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发特性方面非常有效[^5]。并行流和 `ComplempleFutures` 旨在支持这些原则。只要你不决定共享数据(共享非常容易导致意外发生)你就可以编写出相对安全的并发程序。 +**CompletableFutures** 的一个重要好处是它们鼓励使用私有子类原则(不共享任何东西)。默认情况下,使用 **thenApply()** 来应用一个不对外通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发特性方面非常有效[^5]。并行流和 `ComplempleFutures` 旨在支持这些原则。只要你不决定共享数据(共享非常容易导致意外发生)你就可以编写出相对安全的并发程序。 -回调 `thenApply()` 一旦开始一个操作,在完成所有任务之前,不会完成 **CompletableFuture** 的构建。虽然这有时很有用,但是开始所有任务通常更有价值,这样就可以运行继续前进并执行其他操作。我们可通过`thenApplyAsync()` 来实现此目的: +回调 `thenApply()` 一旦开始一个操作,在完成所有任务之前,不会完成 **CompletableFuture** 的构建。虽然这有时很有用,但是开始所有任务通常更有价值,这样就可以运行继续前进并执行其他操作。我们可通过`thenApplyAsync()` 来实现此目的: ```java // concurrent/CompletableApplyAsync.java @@ -1542,19 +1544,19 @@ Machina0: complete 552 ``` -同步调用(我们通常使用的那种)意味着:“当你完成工作时,才返回”,而异步调用以意味着: “立刻返回并继续后续工作”。 正如你所看到的,`cf` 的创建现在发生的更快。每次调用 `thenApplyAsync()` 都会立刻返回,因此可以进行下一次调用,整个调用链路完成速度比以前快得多。 +同步调用 (我们通常使用的那种) 意味着:“当你完成工作时,才返回”,而异步调用以意味着: “立刻返回并继续后续工作”。 正如你所看到的,`cf` 的创建现在发生的更快。每次调用 `thenApplyAsync()` 都会立刻返回,因此可以进行下一次调用,整个调用链路完成速度比以前快得多。 -事实上,如果没有回调 `cf.join()` 方法,程序会在完成其工作之前退出。而 `cf.join()` 直到cf操作完成之前,阻止 `main()` 进程结束。我们还可以看出本示例大部分时间消耗在 `cf.join()` 这。 +事实上,如果没有回调 `cf.join()` 方法,程序会在完成其工作之前退出。而 `cf.join()` 直到 cf 操作完成之前,阻止 `main()` 进程结束。我们还可以看出本示例大部分时间消耗在 `cf.join()` 这。 这种“立即返回”的异步能力需要 `CompletableFuture` 库进行一些秘密(`client` 无感)工作。特别是,它将你需要的操作链存储为一组回调。当操作的第一个链路(后台操作)完成并返回时,第二个链路(后台操作)必须获取生成的 `Machina` 并开始工作,以此类推! 但这种异步机制没有我们可以通过程序调用栈控制的普通函数调用序列,它的调用链路顺序会丢失,因此它使用一个函数地址来存储的回调来解决这个问题。 -幸运的是,这就是你需要了解的有关回调的全部信息。程序员将这种人为制造的混乱称为 callback hell(回调地狱)。通过异步调用,`CompletableFuture` 帮你管理所有回调。 除非你知道系统的一些具体的变化,否则你更想使用异步调用来实现程序。 +幸运的是,这就是你需要了解的有关回调的全部信息。程序员将这种人为制造的混乱称为 callback hell(回调地狱)。通过异步调用,`CompletableFuture` 帮你管理所有回调。 除非你知道你系统中的一些特定逻辑会导致某些改变,或许你更想使用异步调用来实现程序。 - 其他操作 -当你查看`CompletableFuture`的 `Javadoc` 时,你会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有 `thenApply()`,`thenApplyAsync()` 和第二种形式的 `thenApplyAsync()`,它们使用 `Executor` 来运行任务(在本书中,我们忽略了 `Executor` 选项)。 +当你查看`CompletableFuture`的 `Javadoc` 时,你会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有 `thenApply()`,`thenApplyAsync()` 和第二种形式的 `thenApplyAsync()`,它们使用 `Executor` 来运行任务 (在本书中,我们忽略了 `Executor` 选项)。 -下面的示例展示了所有"基本"操作,这些操作既不涉及组合两个 `CompletableFuture`,也不涉及异常(我们将在后面介绍)。首先,为了提供简洁性和方便性,我们应该重用以下两个实用程序: +下面的示例展示了所有"基本"操作,这些操作既不涉及组合两个 `CompletableFuture`,也不涉及异常 (我们将在后面介绍)。首先,为了提供简洁性和方便性,我们应该重用以下两个实用程序: ```java package onjava; @@ -1681,8 +1683,8 @@ dependents: 2 - `cfi(8)` 使用 `toCompletableFuture()` 从 `CompletionStage` 生成一个`CompletableFuture`。 - `c.complete(9)` 显示了如何通过给它一个结果来完成一个`task`(`future`)(与 `obtrudeValue()` 相对,后者可能会迫使其结果替换该结果)。 - 如果你调用 `CompletableFuture`中的 `cancel()`方法,如果已经完成此任务,则正常结束。 如果尚未完成,则使用 `CancellationException` 完成此 `CompletableFuture`。 - - 如果任务(`future`)完成,则**getNow()**方法返回`CompletableFuture`的完成值,否则返回`getNow()`的替换参数。 - - 最后,我们看一下依赖(`dependents`)的概念。如果我们将两个`thenApplyAsync()`调用链路到`CompletableFuture`上,则依赖项的数量不会增加,保持为1。但是,如果我们另外将另一个`thenApplyAsync()`直接附加到`c`,则现在有两个依赖项:两个一起的链路和另一个单独附加的链路。 + - 如果任务(`future`)完成,则 **getNow()** 方法返回`CompletableFuture`的完成值,否则返回`getNow()`的替换参数。 + - 最后,我们看一下依赖 (`dependents`) 的概念。如果我们将两个`thenApplyAsync()`调用链路到`CompletableFuture`上,则依赖项的数量不会增加,保持为 1。但是,如果我们另外将另一个`thenApplyAsync()`直接附加到`c`,则现在有两个依赖项:两个一起的链路和另一个单独附加的链路。 - 这表明你可以使用一个`CompletionStage`,当它完成时,可以根据其结果派生多个新任务。 @@ -1728,7 +1730,7 @@ public class Workable { } ``` -在 `make()`中,`work()`方法应用于`CompletableFuture`。`work()`需要一定的时间才能完成,然后它将字母W附加到id上,表示工作已经完成。 +在 `make()`中,`work()`方法应用于`CompletableFuture`。`work()`需要一定的时间才能完成,然后它将字母 W 附加到 id 上,表示工作已经完成。 现在我们可以创建多个竞争的 `CompletableFuture`,并使用 `CompletableFuture` 库中的各种方法来进行操作: @@ -1857,7 +1859,7 @@ thenAcceptBoth: Workable[AW], Workable[BW] - `init()`方法用于 `A`, `B` 初始化这两个变量,因为 `B` 总是给出比`A`较短的延迟,所以总是 `win` 的一方。 - `join()` 是在两个方法上调用 `join()` 并显示边框的另一个便利方法。 - 所有这些 “`dual`” 方法都以一个 `CompletableFuture` 作为调用该方法的对象,第二个 `CompletableFuture` 作为第一个参数,然后是要执行的操作。 -- 通过使用 `showr()` 和 `voidr()` 可以看到,“`run`”和“`accept`”是终端操作,而“`apply`”和“`combine`”则生成新的 `payload-bearing` (承载负载)的 `CompletableFuture`。 +- 通过使用 `showr()` 和 `voidr()` 可以看到,“`run`”和“`accept`”是终端操作,而“`apply`”和“`combine`”则生成新的 `payload-bearing` (承载负载) 的 `CompletableFuture`。 - 方法的名称不言自明,你可以通过查看输出来验证这一点。一个特别有趣的方法是 `combineAsync()`,它等待两个 `CompletableFuture` 完成,然后将它们都交给一个 `BiFunction`,这个 `BiFunction` 可以将结果加入到最终的 `CompletableFuture` 的有效负载中。 @@ -2112,7 +2114,7 @@ done? false java.lang.RuntimeException: forced ``` -测试 `A` 到 `E` 运行到抛出异常,然后…并没有将抛出的异常暴露给调用方。只有在测试F中调用 `get()` 时,我们才会看到抛出的异常。 +测试 `A` 到 `E` 运行到抛出异常,然后…并没有将抛出的异常暴露给调用方。只有在测试 F 中调用 `get()` 时,我们才会看到抛出的异常。 测试 `G` 表明,你可以首先检查在处理期间是否抛出异常,而不抛出该异常。然而,test `H` 告诉我们,不管异常是否成功,它仍然被视为已“完成”。 代码的最后一部分展示了如何将异常插入到 `CompletableFuture` 中,而不管是否存在任何失败。 在连接或获取结果时,我们使用 `CompletableFuture` 提供的更复杂的机制来自动响应异常,而不是使用粗糙的 `try-catch`。 @@ -2206,16 +2208,16 @@ result: Breakable_whenComplete [-4] - `exceptionally()` 通过将一个好的对象插入到流中来恢复到一个可行的状态。 -- `handle()` 一致被调用来查看是否发生异常(必须检查fail是否为true)。 +- `handle()` 一致被调用来查看是否发生异常(必须检查 fail 是否为 true)。 - 但是 `handle()` 可以生成任何新类型,所以它允许执行处理,而不是像使用 `exceptionally()`那样简单地恢复。 - - `whenComplete()` 类似于handle(),同样必须测试它是否失败,但是参数是一个消费者,并且不修改传递给它的结果对象。 + - `whenComplete()` 类似于 handle(),同样必须测试它是否失败,但是参数是一个消费者,并且不修改传递给它的结果对象。 ### 流异常(Stream Exception) -通过修改**CompletableExceptions.java**,看看 **CompletableFuture**异常与流异常有何不同: +通过修改 **CompletableExceptions.java** ,看看 **CompletableFuture** 异常与流异常有何不同: ```java // concurrent/StreamExceptions.java @@ -2259,7 +2261,7 @@ Throwing Exception for C Breakable_C failed ``` -使用 `CompletableFuture`,我们可以看到测试A到E的进展,但是使用流,在你应用一个终端操作之前(e.g. `forEach()`),什么都不会暴露给 Client +使用 `CompletableFuture`,我们可以看到测试 A 到 E 的进展,但是使用流,在你应用一个终端操作之前(e.g. `forEach()`),什么都不会暴露给 Client `CompletableFuture` 执行工作并捕获任何异常供以后检索。比较这两者并不容易,因为 `Stream` 在没有终端操作的情况下根本不做任何事情——但是流绝对不会存储它的异常。 @@ -2314,7 +2316,7 @@ public class ThrowsChecked { } ``` -如果你试图像使用 `nochecked()` 那样使用` withchecked()` 的方法引用,编译器会在 `[1]` 和 `[2]` 中报错。相反,你必须写出lambda表达式(或者编写一个不会抛出异常的包装器方法)。 +如果你试图像使用 `nochecked()` 那样使用` withchecked()` 的方法引用,编译器会在 `[1]` 和 `[2]` 中报错。相反,你必须写出 lambda 表达式 (或者编写一个不会抛出异常的包装器方法)。 ## 死锁 @@ -2323,7 +2325,7 @@ public class ThrowsChecked { 埃德斯·迪克斯特拉(`Essger Dijkstra`)发明的“哲学家进餐"问题是经典的死锁例证。基本描述指定了五位哲学家(此处显示的示例允许任何数目)。这些哲学家将花部分时间思考,花部分时间就餐。他们在思考的时候并不需要任何共享资源;但是他们使用的餐具数量有限。在最初的问题描述中,餐具是叉子,需要两个叉子才能从桌子中间的碗里取出意大利面。常见的版本是使用筷子, 显然,每个哲学家都需要两根筷子才能吃饭。 引入了一个困难:作为哲学家,他们的钱很少,所以他们只能买五根筷子(更一般地讲,筷子的数量与哲学家相同)。他们围在桌子周围,每人之间放一根筷子。 当一个哲学家要就餐时,该哲学家必须同时持有左边和右边的筷子。如果任一侧的哲学家都在使用所需的筷子,则我们的哲学家必须等待,直到可得到必须的筷子。 -**StickHolder** 类通过将单根筷子保持在大小为1的**BlockingQueue**中来管理它。**BlockingQueue**是一个设计用于在并发程序中安全使用的集合,如果你调用take()并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值: +**StickHolder** 类通过将单根筷子保持在大小为 1 的 **BlockingQueue** 中来管理它。**BlockingQueue** 是一个设计用于在并发程序中安全使用的集合,如果你调用 take() 并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值: ```java // concurrent/StickHolder.java @@ -2397,7 +2399,7 @@ public class Philosopher implements Runnable { } ``` -没有两个哲学家可以同时成功调用take()同一只筷子。另外,如果一个哲学家已经拿过筷子,那么下一个试图拿起同一根筷子的哲学家将阻塞,等待其被释放。 +没有两个哲学家可以同时成功调用 take() 同一只筷子。另外,如果一个哲学家已经拿过筷子,那么下一个试图拿起同一根筷子的哲学家将阻塞,等待其被释放。 结果是一个看似无辜的程序陷入了死锁。我在这里使用数组而不是集合,只是因为这种语法更简洁: @@ -2437,19 +2439,19 @@ public class DiningPhilosophers { ``` - 当你停止查看输出时,该程序将死锁。但是,根据你的计算机配置,你可能不会看到死锁。看来这取决于计算机上的内核数[^7]。两个核心不会产生死锁,但两核以上却很容易产生死锁。 -- 此行为使该示例更好地说明了死锁,因为你可能正在具有2核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,不能因为你没或不容易看到死锁,这并不意味着此程序不会在2核机器上发生死锁。 该程序仍然有死锁倾向,只是很少发生——可以说是最糟糕的情况,因为问题不容易出现。 +- 此行为使该示例更好地说明了死锁,因为你可能正在具有 2 核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,不能因为你没或不容易看到死锁,这并不意味着此程序不会在 2 核机器上发生死锁。 该程序仍然有死锁倾向,只是很少发生——可以说是最糟糕的情况,因为问题不容易出现。 - 在 `DiningPhilosophers` 的构造方法中,每个哲学家都获得一个左右筷子的引用。除最后一个哲学家外,都是通过把哲学家放在下一双空闲筷子之间来初始化: - - 最后一位哲学家得到了第0根筷子作为他的右筷子,所以圆桌就完成。 - - 那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1]显示了以n为模数选择的右筷子,将最后一个哲学家绕到第一个哲学家的旁边。 + - 最后一位哲学家得到了第 0 根筷子作为他的右筷子,所以圆桌就完成。 + - 那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1] 显示了以 n 为模数选择的右筷子,将最后一个哲学家绕到第一个哲学家的旁边。 - 现在,所有哲学家都可以尝试吃饭,每个哲学家都在旁边等待哲学家放下筷子。 - - 为了让每个哲学家在[3]上运行,调用 `runAsync()`,这意味着DiningPhilosophers的构造函数立即返回到[4]。 + - 为了让每个哲学家在[3] 上运行,调用 `runAsync()`,这意味着 DiningPhilosophers 的构造函数立即返回到[4]。 - 如果没有任何东西阻止 `main()` 完成,程序就会退出,不会做太多事情。 - - `Nap` 对象阻止 `main()` 退出,然后在三秒后强制退出(假设/可能是)死锁程序。 + - `Nap` 对象阻止 `main()` 退出,然后在三秒后强制退出 (假设/可能是) 死锁程序。 - 在给定的配置中,哲学家几乎不花时间思考。因此,他们在吃东西的时候都争着用筷子,而且往往很快就会陷入僵局。你可以改变这个: -1. 通过增加[4]的值来添加更多哲学家。 +1. 通过增加[4] 的值来添加更多哲学家。 -2. 在Philosopher.java中取消注释行[1]。 +2. 在 Philosopher.java 中取消注释行[1]。 任一种方法都会减少死锁的可能性,这表明编写并发程序并认为它是安全的危险,因为它似乎“在我的机器上运行正常”。你可以轻松地说服自己该程序没有死锁,即使它不是。这个示例相当有趣,因为它演示了看起来可以正确运行,但实际上会可能发生死锁的程序。 @@ -2462,7 +2464,7 @@ public class DiningPhilosophers { 因为必须满足所有条件才能导致死锁,所以要阻止死锁的话,只需要破坏其中一个即可。在此程序中,防止死锁的一种简单方法是打破第四个条件。之所以会发生这种情况,是因为每个哲学家都尝试按照特定的顺序拾起自己的筷子:先右后左。因此,每个哲学家都有可能在等待左手的同时握住右手的筷子,从而导致循环等待状态。但是,如果其中一位哲学家尝试首先拿起左筷子,则该哲学家决不会阻止紧邻右方的哲学家拿起筷子,从而排除了循环等待。 -在**DiningPhilosophers.java**中,取消注释[1]和其后的一行。这将原来的哲学家[1]替换为筷子颠倒的哲学家。通过确保第二位哲学家拾起并在右手之前放下左筷子,我们消除了死锁的可能性。 +在 **DiningPhilosophers.java** 中,取消注释[1] 和其后的一行。这将原来的哲学家[1] 替换为筷子颠倒的哲学家。通过确保第二位哲学家拾起并在右手之前放下左筷子,我们消除了死锁的可能性。 这只是解决问题的一种方法。你也可以通过防止其他情况之一来解决它。 没有语言支持可以帮助防止死锁;你有责任通过精心设计来避免这种情况。对于试图调试死锁程序的人来说,这些都不是安慰。当然,避免并发问题的最简单,最好的方法是永远不要共享资源-不幸的是,这并不总是可能的。 @@ -2470,11 +2472,11 @@ public class DiningPhilosophers { ## 构造方法非线程安全 -当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前对外不可见,所以又怎会对此产生争议呢?确实,[Java 语言规范](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.3) (JLS)自信满满地陈述道:“*没必要使构造器的线程同步,因为它会锁定正在构造的对象,直到构造器完成初始化后才对其他线程可见。*” +当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前对外不可见,所以又怎会对此产生争议呢?确实,[Java 语言规范 ](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.3) (JLS) 自信满满地陈述道:“*没必要使构造器的线程同步,因为它会锁定正在构造的对象,直到构造器完成初始化后才对其他线程可见。*” 不幸的是,对象的构造过程如其他操作一样,也会受到共享内存并发问题的影响,只是作用机制可能更微妙罢了。 -设想下使用一个 **static** 字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例: +设想下使用一个 **static** 字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例: ```java //concurrent/HasID.java @@ -2483,7 +2485,7 @@ public interface HasID { } ``` -然后 **StaticIDField** 类显式地实现该接口。代码示例: +然后 **StaticIDField** 类显式地实现该接口。代码示例: ```java // concurrent/StaticIDField.java @@ -2539,11 +2541,11 @@ public class IDChecker { } ``` -**MakeObjects** 类是一个生产者类,包含一个能够产生 List\ 类型的列表对象的 `get()` 方法。通过从每个 `HasID` 对象提取 `ID` 并放入列表中来生成这个列表对象,而 `test()` 方法则创建了两个并行的 **CompletableFuture** 对象,用于运行 **MakeObjects** 生产者类,然后获取运行结果。 +**MakeObjects** 类是一个生产者类,包含一个能够产生 List\ 类型的列表对象的 `get()` 方法。通过从每个 `HasID` 对象提取 `ID` 并放入列表中来生成这个列表对象,而 `test()` 方法则创建了两个并行的 **CompletableFuture** 对象,用于运行 **MakeObjects** 生产者类,然后获取运行结果。 使用 Guava 库中的 **Sets.`intersection()` 方法,计算出这两个返回的 List\ 对象中有多少相同的 `ID`(使用谷歌 Guava 库里的方法比使用官方的 `retainAll()` 方法速度快得多)。 -现在我们可以测试上面的 **StaticIDField** 类了。代码示例: +现在我们可以测试上面的 **StaticIDField** 类了。代码示例: ```java // concurrent/TestStaticIDField.java @@ -2561,7 +2563,7 @@ public class TestStaticIDField { 13287 ``` -结果中出现了很多重复项。很显然,纯静态 `int` 用于构造过程并不是线程安全的。让我们使用 **AtomicInteger** 来使其变为线程安全的。代码示例: +结果中出现了很多重复项。很显然,纯静态 `int` 用于构造过程并不是线程安全的。让我们使用 **AtomicInteger** 来使其变为线程安全的。代码示例: ```java // concurrent/GuardedIDField.java @@ -2641,9 +2643,9 @@ public class SharedConstructorArgument{ 0 ``` -在这里,**SharedUser** 构造器实际上共享了相同的参数。即使 **SharedUser** 以完全无害且合理的方式使用其自己的参数,其构造器的调用方式也会引起冲突。**SharedUser** 甚至不知道它是以这种方式调用的,更不必说控制它了。 +在这里,**SharedUser** 构造器实际上共享了相同的参数。即使 **SharedUser** 以完全无害且合理的方式使用其自己的参数,其构造器的调用方式也会引起冲突。**SharedUser** 甚至不知道它是以这种方式调用的,更不必说控制它了。 -同步构造器并不被java语言所支持,但是通过使用同步语块来创建你自己的同步构造器是可能的(请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md),来进一步了解同步关键字—— `synchronized`)。尽管JLS(java语言规范)这样陈述道:“……它会锁定正在构造的对象”,但这并不是真的——构造器实际上只是一个静态方法,因此同步构造器实际上会锁定该类的Class对象。我们可以通过创建自己的静态对象并锁定它,来达到与同步构造器相同的效果: +同步构造器并不被 java 语言所支持,但是通过使用同步语块来创建你自己的同步构造器是可能的(请参阅附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md),来进一步了解同步关键字—— `synchronized`)。尽管 JLS(java 语言规范)这样陈述道:“……它会锁定正在构造的对象”,但这并不是真的——构造器实际上只是一个静态方法,因此同步构造器实际上会锁定该类的 Class 对象。我们可以通过创建自己的静态对象并锁定它,来达到与同步构造器相同的效果: ```java // concurrent/SynchronizedConstructor.java @@ -2681,7 +2683,7 @@ public class SynchronizedConstructor{ 0 ``` -**Unsafe**类的共享使用现在就变得安全了。另一种方法是将构造器设为私有(因此可以防止继承),并提供一个静态Factory方法来生成新对象: +**Unsafe** 类的共享使用现在就变得安全了。另一种方法是将构造器设为私有(因此可以防止继承),并提供一个静态 Factory 方法来生成新对象: ```java // concurrent/SynchronizedFactory.java @@ -2718,9 +2720,9 @@ public class SynchronizedFactory{ 0 ``` -通过同步静态工厂方法,可以在构造过程中锁定 **Class** 对象。 +通过同步静态工厂方法,可以在构造过程中锁定 **Class** 对象。 -这些示例充分表明了在并发Java程序中检测和管理共享状态有多困难。即使你采取“不共享任何内容”的策略,也很容易产生意外的共享事件。 +这些示例充分表明了在并发 Java 程序中检测和管理共享状态有多困难。即使你采取“不共享任何内容”的策略,也很容易产生意外的共享事件。 ## 复杂性和代价 @@ -2806,7 +2808,7 @@ public class Pizza{ } ``` -这只算得上是一个平凡的状态机,就像**Machina**类一样。 +这只算得上是一个平凡的状态机,就像 **Machina** 类一样。 制作一个披萨,当披萨饼最终被放在盒子中时,就算完成最终任务了。 如果一个人在做一个披萨饼,那么所有步骤都是线性进行的,即一个接一个地进行: @@ -2900,9 +2902,9 @@ Pizza 3:BOXED 1739 ``` -现在,我们制作五个披萨的时间与制作单个披萨的时间就差不多了。 尝试删除标记为[1]的行后,你会发现它花费的时间是原来的五倍。 你还可以尝试将**QUANTITY**更改为4、8、10、16和17,看看会有什么不同,并猜猜看为什么会这样。 +现在,我们制作五个披萨的时间与制作单个披萨的时间就差不多了。 尝试删除标记为[1] 的行后,你会发现它花费的时间是原来的五倍。 你还可以尝试将 **QUANTITY** 更改为 4、8、10、16 和 17,看看会有什么不同,并猜猜看为什么会这样。 -**PizzaStreams** 类产生的每个并行流在它的`forEach()`内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗? +**PizzaStreams** 类产生的每个并行流在它的`forEach()`内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗? ```java // concurrent/PizzaParallelSteps.java @@ -2980,7 +2982,7 @@ Pizza3: complete 答案是“否”,事后看来这并不奇怪,因为每个披萨都需要按顺序执行步骤。因此,没法通过分步执行操作来进一步提高速度,就像上文的 `PizzaParallelSteps.java` 里面展示的一样。 -我们可以使用 **CompletableFutures** 重写这个例子: +我们可以使用 **CompletableFutures** 重写这个例子: ```java // concurrent/CompletablePizza.java @@ -3074,19 +3076,19 @@ Pizza4: complete 1797 ``` -并行流和 **CompletableFutures** 是 Java 并发工具箱中最先进发达的技术。 你应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果你决定不借助它而由自己完成,你就必须撸起袖子,深入研究**Spliterator**的文档)。 +并行流和 **CompletableFutures** 是 Java 并发工具箱中最先进发达的技术。 你应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果你决定不借助它而由自己完成,你就必须撸起袖子,深入研究 **Spliterator** 的文档)。 -而当工作的各个部分内容各不相同时,使用 **CompletableFutures** 是最好的选择。比起面向数据,**CompletableFutures** 更像是面向任务的。 +而当工作的各个部分内容各不相同时,使用 **CompletableFutures** 是最好的选择。比起面向数据,**CompletableFutures** 更像是面向任务的。 对于披萨问题,结果似乎也没有什么不同。实际上,并行流方法看起来更简洁,仅出于这个原因,我认为并行流作为解决问题的首次尝试方法更具吸引力。 -由于制作披萨总需要一定的时间,无论你使用哪种并发方法,你能做到的最好情况,是在制作一个披萨的相同时间内制作n个披萨。 在这里当然很容易看出来,但是当你处理更复杂的问题时,你就可能忘记这一点。 通常,在项目开始时进行粗略的计算,就能很快弄清楚最大可能的并行吞吐量,这可以防止你因为采取无用的加快运行速度的举措而忙得团团转。 +由于制作披萨总需要一定的时间,无论你使用哪种并发方法,你能做到的最好情况,是在制作一个披萨的相同时间内制作 n 个披萨。 在这里当然很容易看出来,但是当你处理更复杂的问题时,你就可能忘记这一点。 通常,在项目开始时进行粗略的计算,就能很快弄清楚最大可能的并行吞吐量,这可以防止你因为采取无用的加快运行速度的举措而忙得团团转。 -使用 **CompletableFutures** 或许可以轻易地带来重大收益,但是在尝试更进一步时需要倍加小心,因为额外增加的成本和工作量会非常容易远远超出你之前拼命挤出的那一点点收益。 +使用 **CompletableFutures** 或许可以轻易地带来重大收益,但是在尝试更进一步时需要倍加小心,因为额外增加的成本和工作量会非常容易远远超出你之前拼命挤出的那一点点收益。 ## 本章小结 -需要并发的唯一理由是“等待太多”。这也可以包括用户界面的响应速度,但是由于Java用于构建用户界面时并不高效,因此[^8]这仅仅意味着“你的程序运行速度还不够快”。 +需要并发的唯一理由是“等待太多”。这也可以包括用户界面的响应速度,但是由于 Java 用于构建用户界面时并不高效,因此[^8] 这仅仅意味着“你的程序运行速度还不够快”。 如果并发很容易,则没有理由拒绝并发。 正因为并发实际上很难,所以你应该仔细考虑是否值得为此付出努力,并考虑你能否以其他方式提升速度。 @@ -3102,7 +3104,7 @@ Pizza4: complete 1. 在线程等待共享资源时会降低速度。 -2. 线程管理产生额外CPU开销。 +2. 线程管理产生额外 CPU 开销。 3. 糟糕的设计决策带来无法弥补的复杂性。 @@ -3110,7 +3112,7 @@ Pizza4: complete 5. 跨平台的不一致。 通过一些示例,我发现了某些计算机上很快出现的竞争状况,而在其他计算机上却没有。 如果你在后者上开发程序,则在分发程序时可能会感到非常惊讶。 -另外,并发的应用是一门艺术。 Java旨在允许你创建尽可能多的所需要的对象来解决问题——至少在理论上是这样。[^9]但是,线程不是典型的对象:每个线程都有其自己的执行环境,包括堆栈和其他必要的元素,使其比普通对象大得多。 在大多数环境中,只能在内存用光之前创建数千个**Thread**对象。通常,你只需要几个线程即可解决问题,因此一般来说创建线程没有什么限制,但是对于某些设计而言,它会成为一种约束,可能迫使你使用完全不同的方案。 +另外,并发的应用是一门艺术。 Java 旨在允许你创建尽可能多的所需要的对象来解决问题——至少在理论上是这样。[^9] 但是,线程不是典型的对象:每个线程都有其自己的执行环境,包括堆栈和其他必要的元素,使其比普通对象大得多。 在大多数环境中,只能在内存用光之前创建数千个 **Thread** 对象。通常,你只需要几个线程即可解决问题,因此一般来说创建线程没有什么限制,但是对于某些设计而言,它会成为一种约束,可能迫使你使用完全不同的方案。 ### 共享内存陷阱 @@ -3118,49 +3120,49 @@ Pizza4: complete 我花了多年的时间研究并发。 我了解到你永远无法相信使用共享内存并发的程序可以正常工作。 你可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10] -我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个CPU的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对Java工具很了解。 而且在我的单CPU计算机上也没有失败。而到了具有多个CPU的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是Java的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上你可以在单CPU机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多CPU机器上,就会出现一些其他问题。 +我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个 CPU 的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对 Java 工具很了解。 而且在我的单 CPU 计算机上也没有失败。而到了具有多个 CPU 的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是 Java 的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上你可以在单 CPU 机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多 CPU 机器上,就会出现一些其他问题。 再举一个例子,哲学家就餐的问题可以很容易地进行调整,因此几乎不会产生死锁,这会给你一种一切都棒极了的印象。当涉及到共享内存并发编程时,你永远不应该对自己的编程能力变得过于自信。 ### This Albatross is Big -如果你对Java并发感到不知所措,那说明你身处在一家出色的公司里。你可以访问**Thread**类的[Javadoc](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html)页面, 看一下哪些方法现在是**Deprecated**(废弃的)。这些是Java语言设计者犯过错的地方,因为他们在设计语言时对并发性了解不足。 +如果你对 Java 并发感到不知所措,那说明你身处在一家出色的公司里。你可以访问 **Thread** 类的[Javadoc](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html) 页面, 看一下哪些方法现在是 **Deprecated** (废弃的)。这些是 Java 语言设计者犯过错的地方,因为他们在设计语言时对并发性了解不足。 -事实证明,在Java的后续版本中添加的许多库解决方案都是无效的,甚至是无用的。 幸运的是,Java 8中的并行**Streams**和**CompletableFutures**都非常有价值。但是当你使用旧代码时,仍然会遇到旧的解决方案。 +事实证明,在 Java 的后续版本中添加的许多库解决方案都是无效的,甚至是无用的。 幸运的是,Java 8 中的并行 **Streams** 和 **CompletableFutures** 都非常有价值。但是当你使用旧代码时,仍然会遇到旧的解决方案。 -在本书的其他地方,我谈到了Java的一个基本问题:每个失败的实验都永远嵌入在语言或库中。 Java并发强调了这个问题。尽管有不少错误,但错误并不是那么多,因为有很多不同的尝试方法来解决问题。 好的方面是,这些尝试产生了更好,更简单的设计。 不利之处在于,在找到好的方法之前,你很容易迷失于旧的设计中。 +在本书的其他地方,我谈到了 Java 的一个基本问题:每个失败的实验都永远嵌入在语言或库中。 Java 并发强调了这个问题。尽管有不少错误,但错误并不是那么多,因为有很多不同的尝试方法来解决问题。 好的方面是,这些尝试产生了更好,更简单的设计。 不利之处在于,在找到好的方法之前,你很容易迷失于旧的设计中。 ### 其他类库 -本章重点介绍了相对安全易用的并行工具流和**CompletableFutures**,并且仅涉及Java标准库中一些更细粒度的工具。 为避免你不知所措,我没有介绍你可能实际在实践中使用的某些库。我们使用了几个**Atomic**(原子)类,**ConcurrentLinkedDeque**,**ExecutorService**和**ArrayBlockingQueue**。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)涵盖了其他一些内容,但是你还想探索**java.util.concurrent**的Javadocs。 但是要小心,因为某些库组件已被新的更好的组件所取代。 +本章重点介绍了相对安全易用的并行工具流和 **CompletableFutures** ,并且仅涉及 Java 标准库中一些更细粒度的工具。 为避免你不知所措,我没有介绍你可能实际在实践中使用的某些库。我们使用了几个 **Atomic** (原子)类,**ConcurrentLinkedDeque** ,**ExecutorService** 和 **ArrayBlockingQueue** 。附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md) 涵盖了其他一些内容,但是你还想探索 **java.util.concurrent** 的 Javadocs。 但是要小心,因为某些库组件已被新的更好的组件所取代。 ### 考虑为并发设计的语言 -通常,请谨慎地使用并发。 如果需要使用它,请尝试使用最现代的方法:并行流或**CompletableFutures**。 这些功能旨在(假设你不尝试共享内存)使你摆脱麻烦(在Java的世界范围内)。 +通常,请谨慎地使用并发。 如果需要使用它,请尝试使用最现代的方法:并行流或 **CompletableFutures** 。 这些功能旨在(假设你不尝试共享内存)使你摆脱麻烦(在 Java 的世界范围内)。 -如果你的并发问题变得比高级Java构造所支持的问题更大且更复杂,请考虑使用专为并发设计的语言,仅在需要并发的程序部分中使用这种语言是有可能的。 在撰写本文时,JVM上最纯粹的功能语言是Clojure(Lisp的一种版本)和Frege(Haskell的一种实现)。这些使你可以在其中编写应用程序的并发部分语言,并通过JVM轻松地与你的主要Java代码进行交互。 或者,你可以选择更复杂的方法,即通过外部功能接口(FFI)将JVM之外的语言与另一种为并发设计的语言进行通信。[^11] +如果你的并发问题变得比高级 Java 构造所支持的问题更大且更复杂,请考虑使用专为并发设计的语言,仅在需要并发的程序部分中使用这种语言是有可能的。 在撰写本文时,JVM 上最纯粹的功能语言是 Clojure(Lisp 的一种版本)和 Frege(Haskell 的一种实现)。这些使你可以在其中编写应用程序的并发部分语言,并通过 JVM 轻松地与你的主要 Java 代码进行交互。 或者,你可以选择更复杂的方法,即通过外部功能接口(FFI)将 JVM 之外的语言与另一种为并发设计的语言进行通信。[^11] -你很容易被一种语言绑定,迫使自己尝试使用该语言来做所有事情。 一个常见的示例是构建HTML / JavaScript用户界面。 这些工具确实很难使用,令人讨厌,并且有许多库允许你通过使用自己喜欢的语言编写代码来生成这些工具(例如,**Scala.js**允许你在Scala中完成代码)。 +你很容易被一种语言绑定,迫使自己尝试使用该语言来做所有事情。 一个常见的示例是构建 HTML / JavaScript 用户界面。 这些工具确实很难使用,令人讨厌,并且有许多库允许你通过使用自己喜欢的语言编写代码来生成这些工具(例如,**Scala.js** 允许你在 Scala 中完成代码)。 -心理上的便利是一个合理的考虑因素。 但是,我希望我在本章(以及附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md))中已经表明Java并发是一个你可能无法逃离很深的洞。 与Java语言的任何其他部分相比,在视觉上检查代码同时记住所有陷阱所需要的的知识要困难得多。 +心理上的便利是一个合理的考虑因素。 但是,我希望我在本章(以及附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md))中已经表明 Java 并发是一个你可能无法逃离很深的洞。 与 Java 语言的任何其他部分相比,在视觉上检查代码同时记住所有陷阱所需要的的知识要困难得多。 无论使用特定的语言、库使得并发看起来多么简单,都要将其视为一种妖术,因为总是有东西会在你最不期望出现的时候咬你。 ### 拓展阅读 -《Java Concurrency in Practice》,出自Brian Goetz,Tim Peierls, Joshua Bloch,Joseph Bowbeer,David Holmes和 Doug Lea (Addison Wesley,2006年)——这些基本上就是Java并发世界中的名人名单了《Java Concurrency in Practice》第二版,出自 Doug Lea (Addison-Wesley,2000年)。尽管这本书出版时间远远早于Java 5发布,但Doug的大部分工作都写入了**java.util.concurrent**库。因此,这本书对于全面理解并发问题至关重要。 它超越了Java,讨论了跨语言和技术的并发编程。 尽管它在某些地方可能很钝,但值得多次重读(最好是在两个月之间进行消化)。 道格(Doug)是世界上为数不多的真正了解并发编程的人之一,因此这是值得的。 +《Java Concurrency in Practice》,出自 Brian Goetz,Tim Peierls, Joshua Bloch,Joseph Bowbeer,David Holmes 和 Doug Lea (Addison Wesley,2006 年)——这些基本上就是 Java 并发世界中的名人名单了《Java Concurrency in Practice》第二版,出自 Doug Lea (Addison-Wesley,2000 年)。尽管这本书出版时间远远早于 Java 5 发布,但 Doug 的大部分工作都写入了 **java.util.concurrent** 库。因此,这本书对于全面理解并发问题至关重要。 它超越了 Java,讨论了跨语言和技术的并发编程。 尽管它在某些地方可能很钝,但值得多次重读(最好是在两个月之间进行消化)。 道格(Doug)是世界上为数不多的真正了解并发编程的人之一,因此这是值得的。 -[^1]:例如,Eric-Raymond在“Unix编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。 +[^1]:例如,Eric-Raymond 在“Unix 编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。 [^2]:可以说,试图将并发性用于后续语言是一种注定要失败的方法,但你必须得出自己的结论 -[^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。 +[^3]:有人谈论在 Java——10 中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。 [^4]:这是一种有趣的,虽然不一致的方法。通常,我们期望在公共接口上使用显式类表示不同的行为 -[^5]:不,永远不会有纯粹的功能性Java。我们所能期望的最好的是一种在JVM上运行的全新语言。 +[^5]:不,永远不会有纯粹的功能性 Java。我们所能期望的最好的是一种在 JVM 上运行的全新语言。 [^6]:当两个任务能够更改其状态以使它们不会被阻止但它们从未取得任何有用的进展时,你也可以使用活动锁。 -[^7]: 而不是超线程;通常每个内核有两个超线程,并且在询问内核数量时,本书所使用的Java版本会报告超线程的数量。超线程产生了更快的上下文切换,但是只有实际的内核才真的工作,而不是超线程。 ↩ +[^7]: 而不是超线程;通常每个内核有两个超线程,并且在询问内核数量时,本书所使用的 Java 版本会报告超线程的数量。超线程产生了更快的上下文切换,但是只有实际的内核才真的工作,而不是超线程。 ↩ [^8]: 库就在那里用于调用,而语言本身就被设计用于此目的,但实际上它很少发生,以至于可以说”没有“。↩ -[^9]: 举例来说,如果没有Flyweight设计模式,在工程中创建数百万个对象用于有限元分析可能在Java中不可行。↩ +[^9]: 举例来说,如果没有 Flyweight 设计模式,在工程中创建数百万个对象用于有限元分析可能在 Java 中不可行。↩ [^10]: 在科学中,虽然从来没有一种理论被证实过,但是一种理论必须是可证伪的才有意义。而对于并发性,我们大部分时间甚至都无法得到这种可证伪性。↩ -[^11]: 尽管**Go**语言显示了FFI的前景,但在撰写本文时,它并未提供跨所有平台的解决方案。 +[^11]: 尽管 **Go** 语言显示了 FFI 的前景,但在撰写本文时,它并未提供跨所有平台的解决方案。 diff --git a/docs/book/25-Patterns.md b/docs/book/25-Patterns.md index f3d37647..43c4f929 100644 --- a/docs/book/25-Patterns.md +++ b/docs/book/25-Patterns.md @@ -122,8 +122,26 @@ abstract class ApplicationFramework { abstract void customize1(); - abstract void customize2(); // "private" means automatically "final": private void templateMethod() { IntStream.range(0, 5).forEach( n -> { customize1(); customize2(); }); }}// Create a new "application": class MyApp extends ApplicationFramework { @Override void customize1() { System.out.print("Hello "); }@Override + abstract void customize2(); + + // "private" means automatically "final": + private void templateMethod() { + IntStream.range(0, 5).forEach( + n -> { + customize1(); + customize2(); + }); + } +} +// Create a new "application": +class MyApp extends ApplicationFramework { + @Override + void customize1() { + System.out.print("Hello "); + } + + @Override void customize2() { System.out.println("World!"); } @@ -134,8 +152,7 @@ public class TemplateMethod { new MyApp(); } } -/* -Output: +/* Output: Hello World! Hello World! Hello World! @@ -245,8 +262,20 @@ class State implements StateBase { @Override public void changeImp(StateBase newImp) { implementation = newImp; - }// Pass method calls to the implementation: @Override public void f() { implementation.f(); } @Override public void g() { implementation.g(); } @Override + } + // Pass method calls to the implementation: + @Override + public void f() { + implementation.f(); + } + + @Override + public void g() { + implementation.g(); + } + + @Override public void h() { implementation.h(); } @@ -302,7 +331,8 @@ public class StateDemo { } public static void main(String[] args) { - StateBase b = new State(new Implementation1()); + StateBase b = + new State(new Implementation1()); test(b); b.changeImp(new Implementation2()); test(b); @@ -349,9 +379,6 @@ interface State { abstract class StateMachine { protected State currentState; - Nap(0.5); -System.out.println("Washing"); new - protected abstract boolean changeState(); // Template method: @@ -362,9 +389,12 @@ System.out.println("Washing"); new } // A different subclass for each state: + class Wash implements State { @Override public void run() { + System.out.println("Washing"); + new Nap(0.5); } } @@ -386,9 +416,11 @@ class Rinse implements State { class Washer extends StateMachine { private int i = 0; - // The state table: - private State[] states = {new Wash(), new Spin(), new Rinse(), new Spin(),}; + private State[] states = { + new Wash(), new Spin(), + new Rinse(), new Spin(), + }; Washer() { runAll(); @@ -401,7 +433,8 @@ class Washer extends StateMachine { // surrogate reference to a new object: currentState = states[i++]; return true; - } else return false; + } else + return false; } } @@ -410,8 +443,7 @@ public class StateMachineDemo { new Washer(); } } -/* -Output: +/* Output: Washing Spinning Rinsing diff --git a/docs/book/Appendix-Standard-IO.md b/docs/book/Appendix-Standard-IO.md old mode 100644 new mode 100755 index bfc6d597..842e2804 --- a/docs/book/Appendix-Standard-IO.md +++ b/docs/book/Appendix-Standard-IO.md @@ -35,7 +35,7 @@ public class Echo { `BufferedReader` 提供了 `lines()` 方法,返回类型是 `Stream` 。这显示出流模型的的灵活性:仅使用标准输入就能很好地工作。 `peek()` 方法重启 `TimeAbort`,只要保证至少每隔两秒有输入就能够使程序保持开启状态。 -## 将`System.out` 转换成 `PrintWriter` +## 将 System.out 转换成 PrintWriter `System.out` 是一个 `PrintStream`,而 `PrintStream` 是一个`OutputStream`。 `PrintWriter` 有一个把 `OutputStream` 作为参数的构造器。因此,如果你需要的话,可以使用这个构造器把 `System.out` 转换成 `PrintWriter` 。 diff --git a/docs/book/Appendix-Understanding-equals-and-hashCode.md b/docs/book/Appendix-Understanding-equals-and-hashCode.md index 9cfeb1d5..a5c3dc25 100644 --- a/docs/book/Appendix-Understanding-equals-and-hashCode.md +++ b/docs/book/Appendix-Understanding-equals-and-hashCode.md @@ -69,7 +69,8 @@ import java.util.*; public class Equality { protected int i; protected String s; - protected double d;public Equality(int i, String s, double d) { + protected double d; + public Equality(int i, String s, double d) { this.i = i; this.s = s; this.d = d; @@ -141,7 +142,7 @@ Expected false, got false **testAll()** 执行了我们期望的所有不同类型对象的比较。它使用工厂创建了**Equality**对象。 -在 **main()** 里,请注意对 **testAll()** 的调用很简单。因为**EqualityFactory**有着单一的函数,它能够和lambda表达式一起使用来表示**make()**函数。 +在 **main()** 里,请注意对 **testAll()** 的调用很简单。因为**EqualityFactory**有着单一的函数,它能够和lambda表达式一起使用来表示 **make()** 函数。 上述的 **equals()** 函数非常繁琐,并且我们能够将其简化成规范的形式,请注意: 1. **instanceof**检查减少了**null**检查的需要。 @@ -674,7 +675,7 @@ SlowMap.java 说明了创建一种新的Map并不困难。但是正如它的名 答案就是:数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由定义在Object中的、且可能由你的类覆盖的hashCode()方法(在计算机科学的术语中称为散列函数)生成。 -于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能),那可就有了一个完美的散列函数,但是这种情况只是特例。。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的 list。然后对 list中的值使用equals()方法进行线性的查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不是查询整个list,而是快速地跳到数组的某个位置,只对很少的元素进行比较。这便是HashMap会如此快的原因。 +于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能),那可就有了一个完美的散列函数,但是这种情况只是特例。[^1]。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的 list。然后对 list中的值使用equals()方法进行线性的查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不是查询整个list,而是快速地跳到数组的某个位置,只对很少的元素进行比较。这便是HashMap会如此快的原因。 理解了散列的原理,我们就能够实现一个简单的散列Map了: diff --git a/docs/index.html b/docs/index.html old mode 100644 new mode 100755 index 7f54b8af..a19f3ccd --- a/docs/index.html +++ b/docs/index.html @@ -55,6 +55,11 @@ /*font-family: Microsoft YaHei, Source Sans Pro, Helvetica Neue, Arial, sans-serif !important;*/ } + .sidebar, .sidebar-nav { + padding-left: 10px; + padding-right: 10px; + } + .markdown-section>p { font-size: 16px !important; } @@ -66,7 +71,7 @@ /*.anchor span { color: rgb(66, 185, 131); - }*/ + }*/ section.cover h1 { margin: 0; @@ -345,29 +350,29 @@ window.$docsify = { name: '《On Java 8》中文版', repo: 'https://github.com/LingCoder/OnJava8', - // loadSidebar: true, - subMaxLevel: 3, - search: { - paths: 'auto', - placeholder: '🔍 点击搜索 ', - noData: '😞 没有结果! ', - // Headline depth, 1 - 6 - depth: 6 - }, - copyCode: { - buttonText : '复制', - errorText : 'Error', - successText: 'OK!' - }, - pagination: { - previousText: '上一章节', - nextText: '下一章节', - }, - coverpage: true + loadSidebar: 'sidebar.md', + // subMaxLevel: 3, + search: { + paths: 'auto', + placeholder: '🔍 点击搜索 ', + noData: '😞 没有结果! ', + // Headline depth, 1 - 6 + depth: 6 + }, + copyCode: { + buttonText : '复制', + errorText : 'Error', + successText: 'OK!' + }, + pagination: { + previousText: '上一章节', + nextText: '下一章节', + }, + coverpage: true, } - + diff --git a/docs/sidebar.md b/docs/sidebar.md index d1d99dcd..c7d1abb2 100644 --- a/docs/sidebar.md +++ b/docs/sidebar.md @@ -1,9 +1,14 @@ -#### [译者的话](README.md) -#### [封面](book/00-On-Java-8.md) -#### [前言](book/00-Preface.md) -#### [简介](book/00-Introduction.md) -## [第一章 对象的概念](book/01-What-is-an-Object.md) + +### [译者的话](README.md) + +### [封面](book/00-On-Java-8.md) + +### [前言](book/00-Preface.md) + +### [简介](book/00-Introduction.md) + +### [第一章 对象的概念](book/01-What-is-an-Object.md) * [抽象](book/01-What-is-an-Object.md#抽象) * [接口](book/01-What-is-an-Object.md#接口) * [服务提供](book/01-What-is-an-Object.md#服务提供) @@ -11,18 +16,20 @@ * [复用](book/01-What-is-an-Object.md#复用) * [继承](book/01-What-is-an-Object.md#继承) * [多态](book/01-What-is-an-Object.md#多态) - * [单继承](book/01-What-is-an-Object.md#单继承) + * [单继承结构](book/01-What-is-an-Object.md#单继承结构) * [集合](book/01-What-is-an-Object.md#集合) - * [生命周期](book/01-What-is-an-Object.md#生命周期) + * [对象创建与生命周期](book/01-What-is-an-Object.md#对象创建与生命周期) * [异常处理](book/01-What-is-an-Object.md#异常处理) * [本章小结](book/01-What-is-an-Object.md#本章小结) -## [第二章 安装Java和本书用例](book/02-Installing-Java-and-the-Book-Examples.md) + +### [第二章 安装Java和本书用例](book/02-Installing-Java-and-the-Book-Examples.md) * [编辑器](book/02-Installing-Java-and-the-Book-Examples.md#编辑器) * [Shell](book/02-Installing-Java-and-the-Book-Examples.md#Shell) * [Java安装](book/02-Installing-Java-and-the-Book-Examples.md#Java安装) * [校验安装](book/02-Installing-Java-and-the-Book-Examples.md#校验安装) * [安装和运行代码示例](book/02-Installing-Java-and-the-Book-Examples.md#安装和运行代码示例) -## [第三章 万物皆对象](book/03-Objects-Everywhere.md) + +### [第三章 万物皆对象](book/03-Objects-Everywhere.md) * [对象操纵](book/03-Objects-Everywhere.md#对象操纵) * [对象创建](book/03-Objects-Everywhere.md#对象创建) * [代码注释](book/03-Objects-Everywhere.md#代码注释) @@ -32,7 +39,8 @@ * [小试牛刀](book/03-Objects-Everywhere.md#小试牛刀) * [编码风格](book/03-Objects-Everywhere.md#编码风格) * [本章小结](book/03-Objects-Everywhere.md#本章小结) -## [第四章 运算符](book/04-Operators.md) + +### [第四章 运算符](book/04-Operators.md) * [开始使用](book/04-Operators.md#开始使用) * [优先级](book/04-Operators.md#优先级) * [赋值](book/04-Operators.md#赋值) @@ -41,7 +49,7 @@ * [关系运算符](book/04-Operators.md#关系运算符) * [逻辑运算符](book/04-Operators.md#逻辑运算符) * [字面值常量](book/04-Operators.md#字面值常量) - * [按位运算符](book/04-Operators.md#按位运算符) + * [位运算符](book/04-Operators.md#位运算符) * [移位运算符](book/04-Operators.md#移位运算符) * [三元运算符](book/04-Operators.md#三元运算符) * [字符串运算符](book/04-Operators.md#字符串运算符) @@ -50,18 +58,20 @@ * [Java没有sizeof](book/04-Operators.md#Java没有sizeof) * [运算符总结](book/04-Operators.md#运算符总结) * [本章小结](book/04-Operators.md#本章小结) -## [第五章 控制流](book/05-Control-Flow.md) - * [true和flase](book/05-Control-Flow.md#true和flase) + +### [第五章 控制流](book/05-Control-Flow.md) + * [true和false](book/05-Control-Flow.md#true和false) * [if-else](book/05-Control-Flow.md#if-else) * [迭代语句](book/05-Control-Flow.md#迭代语句) - * [for-in语法](book/05-Control-Flow.md#for-in语法) + * [for-in 语法](book/05-Control-Flow.md#for-in-语法) * [return](book/05-Control-Flow.md#return) - * [break和continue](book/05-Control-Flow.md#break和continue) - * [臭名昭著的goto](book/05-Control-Flow.md#臭名昭著的goto) + * [break 和 continue](book/05-Control-Flow.md#break-和-continue) + * [臭名昭著的 goto](book/05-Control-Flow.md#臭名昭著的-goto) * [switch](book/05-Control-Flow.md#switch) - * [switch字符串](book/05-Control-Flow.md#switch字符串) + * [switch 字符串](book/05-Control-Flow.md#switch-字符串) * [本章小结](book/05-Control-Flow.md#本章小结) -## [第六章 初始化和清理](book/06-Housekeeping.md) + +### [第六章 初始化和清理](book/06-Housekeeping.md) * [利用构造器保证初始化](book/06-Housekeeping.md#利用构造器保证初始化) * [方法重载](book/06-Housekeeping.md#方法重载) * [无参构造器](book/06-Housekeeping.md#无参构造器) @@ -72,13 +82,15 @@ * [数组初始化](book/06-Housekeeping.md#数组初始化) * [枚举类型](book/06-Housekeeping.md#枚举类型) * [本章小结](book/06-Housekeeping.md#本章小结) -## [第七章 封装](book/07-Implementation-Hiding.md) + +### [第七章 封装](book/07-Implementation-Hiding.md) * [包的概念](book/07-Implementation-Hiding.md#包的概念) * [访问权限修饰符](book/07-Implementation-Hiding.md#访问权限修饰符) * [接口和实现](book/07-Implementation-Hiding.md#接口和实现) * [类访问权限](book/07-Implementation-Hiding.md#类访问权限) * [本章小结](book/07-Implementation-Hiding.md#本章小结) -## [第八章 复用](book/08-Reuse.md) + +### [第八章 复用](book/08-Reuse.md) * [组合语法](book/08-Reuse.md#组合语法) * [继承语法](book/08-Reuse.md#继承语法) * [委托](book/08-Reuse.md#委托) @@ -89,14 +101,16 @@ * [final关键字](book/08-Reuse.md#final关键字) * [类初始化和加载](book/08-Reuse.md#类初始化和加载) * [本章小结](book/08-Reuse.md#本章小结) -## [第九章 多态](book/09-Polymorphism.md) - * [向上转型回溯](book/09-Polymorphism.md#向上转型回溯) - * [深入理解](book/09-Polymorphism.md#深入理解) + +### [第九章 多态](book/09-Polymorphism.md) + * [向上转型回顾](book/09-Polymorphism.md#向上转型回顾) + * [转机](book/09-Polymorphism.md#转机) * [构造器和多态](book/09-Polymorphism.md#构造器和多态) - * [返回类型协变](book/09-Polymorphism.md#返回类型协变) + * [协变返回类型](book/09-Polymorphism.md#协变返回类型) * [使用继承设计](book/09-Polymorphism.md#使用继承设计) * [本章小结](book/09-Polymorphism.md#本章小结) -## [第十章 接口](book/10-Interfaces.md) + +### [第十章 接口](book/10-Interfaces.md) * [抽象类和方法](book/10-Interfaces.md#抽象类和方法) * [接口创建](book/10-Interfaces.md#接口创建) * [抽象类和接口](book/10-Interfaces.md#抽象类和接口) @@ -108,21 +122,23 @@ * [接口嵌套](book/10-Interfaces.md#接口嵌套) * [接口和工厂方法模式](book/10-Interfaces.md#接口和工厂方法模式) * [本章小结](book/10-Interfaces.md#本章小结) -## [第十一章 内部类](book/11-Inner-Classes.md) + +### [第十一章 内部类](book/11-Inner-Classes.md) * [创建内部类](book/11-Inner-Classes.md#创建内部类) * [链接外部类](book/11-Inner-Classes.md#链接外部类) - * [内部类this和new的使用](book/11-Inner-Classes.md#内部类this和new的使用) - * [内部类向上转型](book/11-Inner-Classes.md#内部类向上转型) + * [使用 .this 和 .new](book/11-Inner-Classes.md#使用-this-和-new) + * [内部类与向上转型](book/11-Inner-Classes.md#内部类与向上转型) * [内部类方法和作用域](book/11-Inner-Classes.md#内部类方法和作用域) * [匿名内部类](book/11-Inner-Classes.md#匿名内部类) * [嵌套类](book/11-Inner-Classes.md#嵌套类) * [为什么需要内部类](book/11-Inner-Classes.md#为什么需要内部类) * [继承内部类](book/11-Inner-Classes.md#继承内部类) - * [重写内部类](book/11-Inner-Classes.md#重写内部类) - * [内部类局部变量](book/11-Inner-Classes.md#内部类局部变量) + * [内部类可以被覆盖么?](book/11-Inner-Classes.md#内部类可以被覆盖么?) + * [局部内部类](book/11-Inner-Classes.md#局部内部类) * [内部类标识符](book/11-Inner-Classes.md#内部类标识符) * [本章小结](book/11-Inner-Classes.md#本章小结) -## [第十二章 集合](book/12-Collections.md) + +### [第十二章 集合](book/12-Collections.md) * [泛型和类型安全的集合](book/12-Collections.md#泛型和类型安全的集合) * [基本概念](book/12-Collections.md#基本概念) * [添加元素组](book/12-Collections.md#添加元素组) @@ -137,7 +153,8 @@ * [集合与迭代器](book/12-Collections.md#集合与迭代器) * [for-in和迭代器](book/12-Collections.md#for-in和迭代器) * [本章小结](book/12-Collections.md#本章小结) -## [第十三章 函数式编程](book/13-Functional-Programming.md) + +### [第十三章 函数式编程](book/13-Functional-Programming.md) * [新旧对比](book/13-Functional-Programming.md#新旧对比) * [Lambda表达式](book/13-Functional-Programming.md#Lambda表达式) * [方法引用](book/13-Functional-Programming.md#方法引用) @@ -148,37 +165,41 @@ * [柯里化和部分求值](book/13-Functional-Programming.md#柯里化和部分求值) * [纯函数式编程](book/13-Functional-Programming.md#纯函数式编程) * [本章小结](book/13-Functional-Programming.md#本章小结) -## [第十四章 流式编程](book/14-Streams.md) + +### [第十四章 流式编程](book/14-Streams.md) * [流支持](book/14-Streams.md#流支持) * [流创建](book/14-Streams.md#流创建) - * [中级流操作](book/14-Streams.md#中级流操作) + * [中间操作](book/14-Streams.md#中间操作) * [Optional类](book/14-Streams.md#Optional类) * [终端操作](book/14-Streams.md#终端操作) * [本章小结](book/14-Streams.md#本章小结) -## [第十五章 异常](book/15-Exceptions.md) + +### [第十五章 异常](book/15-Exceptions.md) * [异常概念](book/15-Exceptions.md#异常概念) * [基本异常](book/15-Exceptions.md#基本异常) * [异常捕获](book/15-Exceptions.md#异常捕获) * [自定义异常](book/15-Exceptions.md#自定义异常) - * [异常规范](book/15-Exceptions.md#异常规范) - * [任意异常捕获](book/15-Exceptions.md#任意异常捕获) - * [Java标准异常](book/15-Exceptions.md#Java标准异常) - * [finally关键字](book/15-Exceptions.md#finally关键字) + * [异常声明](book/15-Exceptions.md#异常声明) + * [捕获所有异常](book/15-Exceptions.md#捕获所有异常) + * [Java 标准异常](book/15-Exceptions.md#Java-标准异常) + * [使用 finally 进行清理](book/15-Exceptions.md#使用-finally-进行清理) * [异常限制](book/15-Exceptions.md#异常限制) - * [异常构造](book/15-Exceptions.md#异常构造) - * [Try-With-Resources用法](book/15-Exceptions.md#Try-With-Resources用法) + * [构造器](book/15-Exceptions.md#构造器) + * [Try-With-Resources 用法](book/15-Exceptions.md#Try-With-Resources-用法) * [异常匹配](book/15-Exceptions.md#异常匹配) - * [异常准则](book/15-Exceptions.md#异常准则) + * [其他可选方式](book/15-Exceptions.md#其他可选方式) * [异常指南](book/15-Exceptions.md#异常指南) * [本章小结](book/15-Exceptions.md#本章小结) -## [第十六章 代码校验](book/16-Validating-Your-Code.md) + * [后记:Exception Bizarro World](book/15-Exceptions.md#后记:Exception-Bizarro-World) + +### [第十六章 代码校验](book/16-Validating-Your-Code.md) * [测试](book/16-Validating-Your-Code.md#测试) - * [前提条件](book/16-Validating-Your-Code.md#前提条件) + * [前置条件](book/16-Validating-Your-Code.md#前置条件) * [测试驱动开发](book/16-Validating-Your-Code.md#测试驱动开发) * [日志](book/16-Validating-Your-Code.md#日志) * [调试](book/16-Validating-Your-Code.md#调试) * [基准测试](book/16-Validating-Your-Code.md#基准测试) - * [分析和优化](book/16-Validating-Your-Code.md#分析和优化) + * [剖析和优化](book/16-Validating-Your-Code.md#剖析和优化) * [风格检测](book/16-Validating-Your-Code.md#风格检测) * [静态错误分析](book/16-Validating-Your-Code.md#静态错误分析) * [代码重审](book/16-Validating-Your-Code.md#代码重审) @@ -186,7 +207,8 @@ * [重构](book/16-Validating-Your-Code.md#重构) * [持续集成](book/16-Validating-Your-Code.md#持续集成) * [本章小结](book/16-Validating-Your-Code.md#本章小结) -## [第十七章 文件](book/17-Files.md) + +### [第十七章 文件](book/17-Files.md) * [文件和目录路径](book/17-Files.md#文件和目录路径) * [目录](book/17-Files.md#目录) * [文件系统](book/17-Files.md#文件系统) @@ -194,46 +216,51 @@ * [文件查找](book/17-Files.md#文件查找) * [文件读写](book/17-Files.md#文件读写) * [本章小结](book/17-Files.md#本章小结) -## [第十八章 字符串](book/18-Strings.md) + +### [第十八章 字符串](book/18-Strings.md) * [字符串的不可变](book/18-Strings.md#字符串的不可变) - * [重载和StringBuilder](book/18-Strings.md#重载和StringBuilder) + * [+ 的重载与 StringBuilder](book/18-Strings.md#-的重载与-StringBuilder) * [意外递归](book/18-Strings.md#意外递归) * [字符串操作](book/18-Strings.md#字符串操作) * [格式化输出](book/18-Strings.md#格式化输出) - * [常规表达式](book/18-Strings.md#常规表达式) + * [正则表达式](book/18-Strings.md#正则表达式) * [扫描输入](book/18-Strings.md#扫描输入) * [StringTokenizer类](book/18-Strings.md#StringTokenizer类) * [本章小结](book/18-Strings.md#本章小结) -## [第十九章 类型信息](book/19-Type-Information.md) - * [运行时类型信息](book/19-Type-Information.md#运行时类型信息) - * [类的对象](book/19-Type-Information.md#类的对象) + +### [第十九章 类型信息](book/19-Type-Information.md) + * [为什么需要 RTTI](book/19-Type-Information.md#为什么需要-RTTI) + * [Class 对象](book/19-Type-Information.md#Class-对象) * [类型转换检测](book/19-Type-Information.md#类型转换检测) * [注册工厂](book/19-Type-Information.md#注册工厂) * [类的等价比较](book/19-Type-Information.md#类的等价比较) - * [反射运行时类信息](book/19-Type-Information.md#反射运行时类信息) + * [反射:运行时类信息](book/19-Type-Information.md#反射:运行时类信息) * [动态代理](book/19-Type-Information.md#动态代理) * [Optional类](book/19-Type-Information.md#Optional类) * [接口和类型](book/19-Type-Information.md#接口和类型) * [本章小结](book/19-Type-Information.md#本章小结) -## [第二十章 泛型](book/20-Generics.md) + +### [第二十章 泛型](book/20-Generics.md) * [简单泛型](book/20-Generics.md#简单泛型) * [泛型接口](book/20-Generics.md#泛型接口) * [泛型方法](book/20-Generics.md#泛型方法) - * [复杂模型构建](book/20-Generics.md#复杂模型构建) + * [构建复杂模型](book/20-Generics.md#构建复杂模型) * [泛型擦除](book/20-Generics.md#泛型擦除) * [补偿擦除](book/20-Generics.md#补偿擦除) * [边界](book/20-Generics.md#边界) * [通配符](book/20-Generics.md#通配符) * [问题](book/20-Generics.md#问题) - * [自我约束类型](book/20-Generics.md#自我约束类型) + * [自限定的类型](book/20-Generics.md#自限定的类型) * [动态类型安全](book/20-Generics.md#动态类型安全) * [泛型异常](book/20-Generics.md#泛型异常) - * [混入](book/20-Generics.md#混入) - * [潜在类型](book/20-Generics.md#潜在类型) - * [补偿不足](book/20-Generics.md#补偿不足) - * [辅助潜在类型](book/20-Generics.md#辅助潜在类型) - * [泛型的优劣](book/20-Generics.md#泛型的优劣) -## [第二十一章 数组](book/21-Arrays.md) + * [混型](book/20-Generics.md#混型) + * [潜在类型机制](book/20-Generics.md#潜在类型机制) + * [对缺乏潜在类型机制的补偿](book/20-Generics.md#对缺乏潜在类型机制的补偿) + * [Java8 中的辅助潜在类型](book/20-Generics.md#Java8-中的辅助潜在类型) + * [总结:类型转换真的如此之糟吗?](book/20-Generics.md#总结:类型转换真的如此之糟吗?) + * [进阶阅读](book/20-Generics.md#进阶阅读) + +### [第二十一章 数组](book/21-Arrays.md) * [数组特性](book/21-Arrays.md#数组特性) * [一等对象](book/21-Arrays.md#一等对象) * [返回数组](book/21-Arrays.md#返回数组) @@ -251,51 +278,57 @@ * [数组比较](book/21-Arrays.md#数组比较) * [流和数组](book/21-Arrays.md#流和数组) * [数组排序](book/21-Arrays.md#数组排序) + * [Arrays.sort 的使用](book/21-Arrays.md#Arrayssort-的使用) + * [并行排序](book/21-Arrays.md#并行排序) * [binarySearch二分查找](book/21-Arrays.md#binarySearch二分查找) * [parallelPrefix并行前缀](book/21-Arrays.md#parallelPrefix并行前缀) * [本章小结](book/21-Arrays.md#本章小结) -## [第二十二章 枚举](book/22-Enumerations.md) - * [基本功能](book/22-Enumerations.md#基本功能) + +### [第二十二章 枚举](book/22-Enumerations.md) + * [基本 enum 特性](book/22-Enumerations.md#基本-enum-特性) * [方法添加](book/22-Enumerations.md#方法添加) - * [switch语句](book/22-Enumerations.md#switch语句) - * [values方法](book/22-Enumerations.md#values方法) + * [switch 语句中的 enum](book/22-Enumerations.md#switch-语句中的-enum) + * [values 方法的神秘之处](book/22-Enumerations.md#values-方法的神秘之处) * [实现而非继承](book/22-Enumerations.md#实现而非继承) * [随机选择](book/22-Enumerations.md#随机选择) - * [使用接口组织](book/22-Enumerations.md#使用接口组织) - * [使用EnumSet替代Flags](book/22-Enumerations.md#使用EnumSet替代Flags) - * [使用EnumMap](book/22-Enumerations.md#使用EnumMap) + * [使用接口组织枚举](book/22-Enumerations.md#使用接口组织枚举) + * [使用 EnumSet 替代 Flags](book/22-Enumerations.md#使用-EnumSet-替代-Flags) + * [使用 EnumMap](book/22-Enumerations.md#使用-EnumMap) * [常量特定方法](book/22-Enumerations.md#常量特定方法) - * [多次调度](book/22-Enumerations.md#多次调度) + * [多路分发](book/22-Enumerations.md#多路分发) * [本章小结](book/22-Enumerations.md#本章小结) -## [第二十三章 注解](book/23-Annotations.md) + +### [第二十三章 注解](book/23-Annotations.md) * [基本语法](book/23-Annotations.md#基本语法) * [编写注解处理器](book/23-Annotations.md#编写注解处理器) * [使用javac处理注解](book/23-Annotations.md#使用javac处理注解) * [基于注解的单元测试](book/23-Annotations.md#基于注解的单元测试) * [本章小结](book/23-Annotations.md#本章小结) -## [第二十四章 并发编程](book/24-Concurrent-Programming.md) + +### [第二十四章 并发编程](book/24-Concurrent-Programming.md) * [术语问题](book/24-Concurrent-Programming.md#术语问题) * [并发的超能力](book/24-Concurrent-Programming.md#并发的超能力) - * [针对速度](book/24-Concurrent-Programming.md#针对速度) + * [并发为速度而生](book/24-Concurrent-Programming.md#并发为速度而生) * [四句格言](book/24-Concurrent-Programming.md#四句格言) * [残酷的真相](book/24-Concurrent-Programming.md#残酷的真相) * [本章其余部分](book/24-Concurrent-Programming.md#本章其余部分) * [并行流](book/24-Concurrent-Programming.md#并行流) * [创建和运行任务](book/24-Concurrent-Programming.md#创建和运行任务) * [终止耗时任务](book/24-Concurrent-Programming.md#终止耗时任务) - * [CompletableFuture类](book/24-Concurrent-Programming.md#CompletableFuture类) + * [CompletableFuture 类](book/24-Concurrent-Programming.md#CompletableFuture-类) * [死锁](book/24-Concurrent-Programming.md#死锁) - * [构造函数非线程安全](book/24-Concurrent-Programming.md#构造函数非线程安全) + * [构造方法非线程安全](book/24-Concurrent-Programming.md#构造方法非线程安全) * [复杂性和代价](book/24-Concurrent-Programming.md#复杂性和代价) * [本章小结](book/24-Concurrent-Programming.md#本章小结) -## [第二十五章 设计模式](book/25-Patterns.md) + +### [第二十五章 设计模式](book/25-Patterns.md) * [概念](book/25-Patterns.md#概念) - * [构建型](book/25-Patterns.md#构建型) - * [面向实施](book/25-Patterns.md#面向实施) + * [构建应用程序框架](book/25-Patterns.md#构建应用程序框架) + * [面向实现](book/25-Patterns.md#面向实现) * [工厂模式](book/25-Patterns.md#工厂模式) * [函数对象](book/25-Patterns.md#函数对象) - * [接口改变](book/25-Patterns.md#接口改变) - * [解释器](book/25-Patterns.md#解释器) + * [改变接口](book/25-Patterns.md#改变接口) + * [解释器:运行时的弹性](book/25-Patterns.md#解释器:运行时的弹性) * [回调](book/25-Patterns.md#回调) * [多次调度](book/25-Patterns.md#多次调度) * [模式重构](book/25-Patterns.md#模式重构) @@ -305,50 +338,29 @@ * [RTTI的优劣](book/25-Patterns.md#RTTI的优劣) * [本章小结](book/25-Patterns.md#本章小结) -## [附录:补充](book/Appendix-Supplements.md) - * [可下载的补充](book/Appendix-Supplements.md#可下载的补充) - * [通过Thinking-in-C来巩固Java基础](book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础) - * [动手实践](book/Appendix-Supplements.md#动手实践) -## [附录:编程指南](book/Appendix-Programming-Guidelines.md) - * [设计](book/Appendix-Programming-Guidelines.md#设计) - * [实现](book/Appendix-Programming-Guidelines.md#实现) -## [附录:文档注释](book/Appendix-Javadoc.md) -## [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md) - * [传递引用](book/Appendix-Passing-and-Returning-Objects.md#传递引用) - * [本地拷贝](book/Appendix-Passing-and-Returning-Objects.md#本地拷贝) - * [控制克隆](book/Appendix-Passing-and-Returning-Objects.md#控制克隆) - * [不可变类](book/Appendix-Passing-and-Returning-Objects.md#不可变类) - * [本章小结](book/Appendix-Passing-and-Returning-Objects.md#本章小结) -## [附录:流式IO](book/Appendix-IO-Streams.md) - * [输入流类型](book/Appendix-IO-Streams.md#输入流类型) - * [输出流类型](book/Appendix-IO-Streams.md#输出流类型) - * [添加属性和有用的接口](book/Appendix-IO-Streams.md#添加属性和有用的接口) - * [Reader和Writer](book/Appendix-IO-Streams.md#Reader和Writer) - * [RandomAccessFile类](book/Appendix-IO-Streams.md#RandomAccessFile类) - * [IO流典型用途](book/Appendix-IO-Streams.md#IO流典型用途) - * [本章小结](book/Appendix-IO-Streams.md#本章小结) -## [附录:标准IO](book/Appendix-Standard-IO.md) - * [执行控制](book/Appendix-Standard-IO.md#执行控制) -## [附录:新IO](book/Appendix-New-IO.md) - * [ByteBuffer](book/Appendix-New-IO.md#ByteBuffer) - * [转换数据](book/Appendix-New-IO.md#数据转换) - * [获取原始类型](book/Appendix-New-IO.md#基本类型获取) - * [视图缓冲区](book/Appendix-New-IO.md#视图缓冲区) - * [使用缓冲区进行数据操作](book/Appendix-New-IO.md#缓冲区数据操作) - * [内存映射文件](book/Appendix-New-IO.md#内存映射文件) - * [文件锁定](book/Appendix-New-IO.md#文件锁定) -## [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md) - * [equals典范](book/Appendix-Understanding-equals-and-hashCode.md#equals典范) - * [哈希和哈希码](book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码) - * [调整HashMap](book/Appendix-Understanding-equals-and-hashCode.md#调整HashMap) -## [附录:集合主题](book/Appendix-Collection-Topics.md) +### [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md) + * [如何开始](book/Appendix-Becoming-a-Programmer.md#如何开始) + * [码农生涯](book/Appendix-Becoming-a-Programmer.md#码农生涯) + * [百分之五的神话](book/Appendix-Becoming-a-Programmer.md#百分之五的神话) + * [重在动手](book/Appendix-Becoming-a-Programmer.md#重在动手) + * [像打字般编程](book/Appendix-Becoming-a-Programmer.md#像打字般编程) + * [做你喜欢的事](book/Appendix-Becoming-a-Programmer.md#做你喜欢的事) + +### [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) + * [前言](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言) + * [静态类型检查和测试](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试) + * [如何提升打字](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字) + * [生产力的成本](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本) + * [静态和动态](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态) + +### [附录:集合主题](book/Appendix-Collection-Topics.md) * [示例数据](book/Appendix-Collection-Topics.md#示例数据) - * [List表现](book/Appendix-Collection-Topics.md#List表现) - * [Set表现](book/Appendix-Collection-Topics.md#Set表现) + * [List行为](book/Appendix-Collection-Topics.md#List行为) + * [Set行为](book/Appendix-Collection-Topics.md#Set行为) * [在Map中使用函数式操作](book/Appendix-Collection-Topics.md#在Map中使用函数式操作) - * [选择Map的部分](book/Appendix-Collection-Topics.md#选择Map的部分) - * [集合的fill方法](book/Appendix-Collection-Topics.md#集合的fill方法) - * [使用Flyweight自定义集合和Map](book/Appendix-Collection-Topics.md#使用Flyweight自定义集合和Map) + * [选择Map片段](book/Appendix-Collection-Topics.md#选择Map片段) + * [填充集合](book/Appendix-Collection-Topics.md#填充集合) + * [使用享元(Flyweight)自定义Collection和Map](book/Appendix-Collection-Topics.md#使用享元(Flyweight)自定义Collection和Map) * [集合功能](book/Appendix-Collection-Topics.md#集合功能) * [可选操作](book/Appendix-Collection-Topics.md#可选操作) * [Set和存储顺序](book/Appendix-Collection-Topics.md#Set和存储顺序) @@ -356,37 +368,81 @@ * [理解Map](book/Appendix-Collection-Topics.md#理解Map) * [集合工具类](book/Appendix-Collection-Topics.md#集合工具类) * [持有引用](book/Appendix-Collection-Topics.md#持有引用) - * [避免旧式类库](book/Appendix-Collection-Topics.md#避免旧式类库) + * [Java 1.0 / 1.1 的集合类](book/Appendix-Collection-Topics.md#Java-10-11-的集合类) * [本章小结](book/Appendix-Collection-Topics.md#本章小结) -## [附录:并发底层原理](book/Appendix-Low-Level-Concurrency.md) - * [线程](book/Appendix-Low-Level-Concurrency.md#线程) + +### [附录:数据压缩](book/Appendix-Data-Compression.md) + * [使用 Gzip 简单压缩](book/Appendix-Data-Compression.md#使用-Gzip-简单压缩) + * [使用 zip 多文件存储](book/Appendix-Data-Compression.md#使用-zip-多文件存储) + * [Java 的 jar](book/Appendix-Data-Compression.md#Java-的-jar) + +### [附录:流式IO](book/Appendix-IO-Streams.md) + * [输入流类型](book/Appendix-IO-Streams.md#输入流类型) + * [输出流类型](book/Appendix-IO-Streams.md#输出流类型) + * [添加属性和有用的接口](book/Appendix-IO-Streams.md#添加属性和有用的接口) + * [Reader和Writer](book/Appendix-IO-Streams.md#Reader和Writer) + * [RandomAccessFile类](book/Appendix-IO-Streams.md#RandomAccessFile类) + * [IO流典型用途](book/Appendix-IO-Streams.md#IO流典型用途) + * [本章小结](book/Appendix-IO-Streams.md#本章小结) + +### [附录:文档注释](book/Appendix-Javadoc.md) + * [句法规则](book/Appendix-Javadoc.md#句法规则) + * [内嵌 HTML](book/Appendix-Javadoc.md#内嵌-HTML) + * [示例标签](book/Appendix-Javadoc.md#示例标签) + * [文档示例](book/Appendix-Javadoc.md#文档示例) + +### [附录:并发底层原理](book/Appendix-Low-Level-Concurrency.md) + * [什么是线程?](book/Appendix-Low-Level-Concurrency.md#什么是线程?) * [异常捕获](book/Appendix-Low-Level-Concurrency.md#异常捕获) * [资源共享](book/Appendix-Low-Level-Concurrency.md#资源共享) - * [volatile关键字](book/Appendix-Low-Level-Concurrency.md#volatile关键字) + * [volatile 关键字](book/Appendix-Low-Level-Concurrency.md#volatile-关键字) * [原子性](book/Appendix-Low-Level-Concurrency.md#原子性) - * [关键部分](book/Appendix-Low-Level-Concurrency.md#关键部分) + * [临界区](book/Appendix-Low-Level-Concurrency.md#临界区) * [库组件](book/Appendix-Low-Level-Concurrency.md#库组件) * [本章小结](book/Appendix-Low-Level-Concurrency.md#本章小结) -## [附录:数据压缩](book/Appendix-Data-Compression.md) - * [使用Gzip简单压缩](book/Appendix-Data-Compression.md#使用Gzip简单压缩) - * [使用zip多文件存储](book/Appendix-Data-Compression.md#使用zip多文件存储) - * [Java的jar](book/Appendix-Data-Compression.md#Java的jar) -## [附录:对象序列化](book/Appendix-Object-Serialization.md) + +### [附录:新IO](book/Appendix-New-IO.md) + * [ByteBuffer](book/Appendix-New-IO.md#ByteBuffer) + * [数据转换](book/Appendix-New-IO.md#数据转换) + * [基本类型获取](book/Appendix-New-IO.md#基本类型获取) + * [视图缓冲区](book/Appendix-New-IO.md#视图缓冲区) + * [缓冲区数据操作](book/Appendix-New-IO.md#缓冲区数据操作) + * [ 内存映射文件](book/Appendix-New-IO.md#-内存映射文件) + * [文件锁定](book/Appendix-New-IO.md#文件锁定) + +### [附录:对象序列化](book/Appendix-Object-Serialization.md) * [查找类](book/Appendix-Object-Serialization.md#查找类) * [控制序列化](book/Appendix-Object-Serialization.md#控制序列化) * [使用持久化](book/Appendix-Object-Serialization.md#使用持久化) -## [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) - * [前言](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言) - * [静态类型检查和测试](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试) - * [如何提升打字](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字) - * [生产力的成本](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本) - * [静态和动态](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态) -## [附录:C++和Java的优良传统](book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) -## [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md) - * [如何开始](book/Appendix-Becoming-a-Programmer.md#如何开始) - * [码农生涯](book/Appendix-Becoming-a-Programmer.md#码农生涯) - * [百分之五的神话](book/Appendix-Becoming-a-Programmer.md#百分之五的神话) - * [重在动手](book/Appendix-Becoming-a-Programmer.md#重在动手) - * [像打字般编程](book/Appendix-Becoming-a-Programmer.md#像打字般编程) - * [做你喜欢的事](book/Appendix-Becoming-a-Programmer.md#做你喜欢的事) -## [词汇表](book/GLOSSARY.md) + * [XML](book/Appendix-Object-Serialization.md#XML) + +### [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md) + * [传递引用](book/Appendix-Passing-and-Returning-Objects.md#传递引用) + * [本地拷贝](book/Appendix-Passing-and-Returning-Objects.md#本地拷贝) + * [控制克隆](book/Appendix-Passing-and-Returning-Objects.md#控制克隆) + * [不可变类](book/Appendix-Passing-and-Returning-Objects.md#不可变类) + * [本章小结](book/Appendix-Passing-and-Returning-Objects.md#本章小结) + +### [附录:编程指南](book/Appendix-Programming-Guidelines.md) + * [设计](book/Appendix-Programming-Guidelines.md#设计) + * [实现](book/Appendix-Programming-Guidelines.md#实现) + +### [附录:标准IO](book/Appendix-Standard-IO.md) + * [从标准输入中读取](book/Appendix-Standard-IO.md#从标准输入中读取) + * [将 System.out 转换成 PrintWriter](book/Appendix-Standard-IO.md#将-Systemout-转换成-PrintWriter) + * [重定向标准 I/O](book/Appendix-Standard-IO.md#重定向标准-IO) + * [执行控制](book/Appendix-Standard-IO.md#执行控制) + +### [附录:补充](book/Appendix-Supplements.md) + * [可下载的补充](book/Appendix-Supplements.md#可下载的补充) + * [通过Thinking-in-C来巩固Java基础](book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础) + * [Hand-On Java 电子演示文稿](book/Appendix-Supplements.md#Hand-On-Java-电子演示文稿) + +### [附录:C++和Java的优良传统](book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) + +### [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md) + * [equals规范](book/Appendix-Understanding-equals-and-hashCode.md#equals规范) + * [哈希和哈希码](book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码) + * [调优 HashMap](book/Appendix-Understanding-equals-and-hashCode.md#调优-HashMap) + +### [词汇表](book/GLOSSARY.md) \ No newline at end of file