## 补偿擦除
因为擦除,我们将失去执行泛型代码中某些操作的能力。无法在运行时知道确切类型:
```java
// generics/Erased.java
// {WillNotCompile}
public class Erased<T> {
private final int SIZE = 100;
public void f(Object arg) {
// error: illegal generic type for instanceof
if (arg instanceof T) {
}
// error: unexpected type
T var = new T();
// error: generic array creation
T[] array = new T[SIZE];
// warning: [unchecked] unchecked cast
T[] array = (T[]) new Object[SIZE];
}
}
```
有时,我们可以对这些问题进行编程,但是有时必须通过引入类型标签来补偿擦除。这意味着为所需的类型显式传递一个 **Class** 对象,以在类型表达式中使用它。
例如,由于擦除了类型信息,因此在上一个程序中尝试使用 **instanceof** 将会失败。类型标签可以使用动态 `isInstance()` :
```java
// generics/ClassTypeCapture.java
class Building {
}
class House extends Building {
}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 =
new ClassTypeCapture<>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 =
new ClassTypeCapture<>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
}
/* Output:
true
true
false
true
*/
```
编译器来保证类型标签与泛型参数相匹配。
<!-- Creating Instances of Types -->
### 创建类型的实例
试图在 **Erased.java** 中 `new T()` 是行不通的,部分原因是由于擦除,部分原因是编译器无法验证 **T** 是否具有默认(无参)构造函数。但是在 C++ 中,此操作自然,直接且安全(在编译时检查):
```C++
// generics/InstantiateGenericType.cpp
// C++, not Java!
template<class T> class Foo {
T x; // Create a field of type T
T* y; // Pointer to T
public:
// Initialize the pointer:
Foo() { y = new T(); }
};
class Bar {};
int main() {
Foo<Bar> fb;
Foo<int> fi; // ... and it works with primitives
}
```
Java 中的解决方案是传入一个工厂对象,并使用该对象创建新实例。方便的工厂对象只是 **Class** 对象,因此,如果使用类型标记,则可以使用 `newInstance()` 创建该类型的新对象:
```java
// generics/InstantiateGenericType.java
import java.util.function.Supplier;
class ClassAsFactory<T> implements Supplier<T> {
Class<T> kind;
ClassAsFactory(Class<T> kind) {
this.kind = kind;
}
@Override
public T get() {
try {
return kind.newInstance();
} catch (InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
class Employee {
@Override
public String toString() {
return "Employee";
}
}
public class InstantiateGenericType {
public static void main(String[] args) {
ClassAsFactory<Employee> fe =
new ClassAsFactory<>(Employee.class);
System.out.println(fe.get());
ClassAsFactory<Integer> fi =
new ClassAsFactory<>(Integer.class);
try {
System.out.println(fi.get());
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
/* Output:
Employee
java.lang.InstantiationException: java.lang.Integer
*/
```
这样可以编译,但对于 `ClassAsFactory<Integer>` 会失败,这是因为 **Integer** 没有无参构造函数。由于错误不是在编译时捕获的,因此语言创建者不赞成这种方法。他们建议使用显式工厂(**Supplier**)并约束类型,以便只有实现该工厂的类可以这样创建对象。这是创建工厂的两种不同方法:
```java
// generics/FactoryConstraint.java
import onjava.Suppliers;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
class IntegerFactory implements Supplier<Integer> {
private int i = 0;
@Override
public Integer get() {
return ++i;
}
}
class Widget {
private int id;
Widget(int n) {
id = n;
}
@Override
public String toString() {
return "Widget " + id;
}
public static
class Factory implements Supplier<Widget> {
private int i = 0;
@Override
public Widget get() {
return new Widget(++i);
}
}
}
class Fudge {
private static int count = 1;
private int n = count++;
@Override
public String toString() {
return "Fudge " + n;
}
}
class Foo2<T> {
private List<T> x = new ArrayList<>();
Foo2(Supplier<T> factory) {
Suppliers.fill(x, factory, 5);
}
@Override
public String toString() {
return x.toString();
}
}
public class FactoryConstraint {
public static void main(String[] args) {
System.out.println(
new Foo2<>(new IntegerFactory()));
System.out.println(
new Foo2<>(new Widget.Factory()));
System.out.println(
new Foo2<>(Fudge::new));
}
}
/* Output:
[1, 2, 3, 4, 5]
[Widget 1, Widget 2, Widget 3, Widget 4, Widget 5]
[Fudge 1, Fudge 2, Fudge 3, Fudge 4, Fudge 5]
*/
```
**IntegerFactory** 本身就是通过实现 `Supplier<Integer>` 的工厂。 **Widget** 包含一个内部类,它是一个工厂。还要注意,**Fudge** 并没有做任何类似于工厂的操作,并且传递 `Fudge::new` 仍然会产生工厂行为,因为编译器将对函数方法 `::new` 的调用转换为对 `get()` 的调用。
另一种方法是模板方法设计模式。在以下示例中,`create()` 是模板方法,在子类中被重写以生成该类型的对象:
```java
// generics/CreatorGeneric.java
abstract class GenericWithCreate<T> {
final T element;
GenericWithCreate() {
element = create();
}
abstract T create();
}
class X {
}
class XCreator extends GenericWithCreate<X> {
@Override
X create() {
return new X();
}
void f() {
System.out.println(
element.getClass().getSimpleName());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
XCreator xc = new XCreator();
xc.f();
}
}
/* Output:
X
*/
```
**GenericWithCreate** 包含 `element` 字段,并通过无参构造函数强制其初始化,该构造函数又调用抽象的 `create()` 方法。这种创建方式可以在子类中定义,同时建立 **T** 的类型。
<!-- Arrays of Generics -->
### 泛型数组
正如在 **Erased.java** 中所看到的,我们无法创建泛型数组。通用解决方案是在试图创建泛型数组的时候使用 **ArrayList** :
```java
// generics/ListOfGenerics.java
import java.util.ArrayList;
import java.util.List;
public class ListOfGenerics<T> {
private List<T> array = new ArrayList<>();
public void add(T item) {
array.add(item);
}
public T get(int index) {
return array.get(index);
}
}
```
这样做可以获得数组的行为,并且还具有泛型提供的编译时类型安全性。
有时,仍然会创建泛型类型的数组(例如, **ArrayList** 在内部使用数组)。可以通过使编译器满意的方式定义对数组的通用引用:
```java
// generics/ArrayOfGenericReference.java
class Generic<T> {
}
public class ArrayOfGenericReference {
static Generic<Integer>[] gia;
}
```
编译器接受此操作而不产生警告。但是我们永远无法创建具有该确切类型(包括类型参数)的数组,因此有点令人困惑。由于所有数组,无论它们持有什么类型,都具有相同的结构(每个数组插槽的大小和数组布局),因此似乎可以创建一个 **Object** 数组并将其转换为所需的数组类型。实际上,这确实可以编译,但是会产生 **ClassCastException** :
```java
// generics/ArrayOfGeneric.java
public class ArrayOfGeneric {
static final int SIZE = 100;
static Generic<Integer>[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
try {
gia = (Generic<Integer>[]) new Object[SIZE];
} catch (ClassCastException e) {
System.out.println(e.getMessage());
}
// Runtime type is the raw (erased) type:
gia = (Generic<Integer>[]) new Generic[SIZE];
System.out.println(gia.getClass().getSimpleName());
gia[0] = new Generic<>();
//- gia[1] = new Object(); // Compile-time error
// Discovers type mismatch at compile time:
//- gia[2] = new Generic<Double>();
}
}
/* Output:
[Ljava.lang.Object; cannot be cast to [LGeneric;
Generic[]
*/
```
问题在于数组会跟踪其实际类型,而该类型是在创建数组时建立的。因此,即使 `gia` 被强制转换为 `Generic<Integer>[]` ,该信息也仅在编译时存在(并且没有 **@SuppressWarnings** 注解,将会收到有关该强制转换的警告)。在运行时,它仍然是一个 **Object** 数组,这会引起问题。成功创建泛型类型的数组的唯一方法是创建一个已擦除类型的新数组,并将其强制转换。
让我们看一个更复杂的示例。考虑一个包装数组的简单泛型包装器:
```java
// generics/GenericArray.java
public class GenericArray<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
array = (T[]) new Object[sz];
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
// Method that exposes the underlying representation:
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArray<Integer> gai = new GenericArray<>(10);
try {
Integer[] ia = gai.rep();
} catch (ClassCastException e) {
System.out.println(e.getMessage());
}
// This is OK:
Object[] oa = gai.rep();
}
}
/* Output:
[Ljava.lang.Object; cannot be cast to
[Ljava.lang.Integer;
*/
```
和以前一样,我们不能说 `T[] array = new T[sz]` ,所以我们创建了一个 **Object** 数组并将其强制转换。
`rep()` 方法返回一个 `T[]` ,在主方法中它应该是 `gai` 的 `Integer[]`,但是如果调用它并尝试将结果转换为 `Integer[]` 引用,则会得到 **ClassCastException** ,这再次是因为实际的运行时类型为 `Object[]` 。
如果再注释掉 **@SuppressWarnings** 注解后编译 **GenericArray.java** ,则编译器会产生警告:
```java
GenericArray.java uses unchecked or unsafe operations.
Recompile with -Xlint:unchecked for details.
```
在这里,我们收到了一个警告,我们认为这是有关强制转换的。
但是要真正确定,请使用 `-Xlint:unchecked` 进行编译:
```java
GenericArray.java:7: warning: [unchecked] unchecked cast array = (T[])new Object[sz]; ^ required: T[] found: Object[] where T is a type-variable: T extends Object declared in class GenericArray 1 warning
```
确实是在抱怨那个强制转换。由于警告会变成噪音,因此,一旦我们确认预期会出现特定警告,我们可以做的最好的办法就是使用 **@SuppressWarnings** 将其关闭。这样,当警告确实出现时,我们将进行实际调查。
由于擦除,数组的运行时类型只能是 `Object[]` 。 如果我们立即将其转换为 `T[]` ,则在编译时会丢失数组的实际类型,并且编译器可能会错过一些潜在的错误检查。因此,最好在集合中使用 `Object[]` ,并在使用数组元素时向 **T** 添加强制类型转换。让我们来看看在 **GenericArray.java** 示例中会是怎么样的:
```java
// generics/GenericArray2.java
public class GenericArray2<T> {
private Object[] array;
public GenericArray2(int sz) {
array = new Object[sz];
}
public void put(int index, T item) {
array[index] = item;
}
@SuppressWarnings("unchecked")
public T get(int index) {
return (T) array[index];
}
@SuppressWarnings("unchecked")
public T[] rep() {
return (T[]) array; // Unchecked cast
}
public static void main(String[] args) {
GenericArray2<Integer> gai =
new GenericArray2<>(10);
for (int i = 0; i < 10; i++)
gai.put(i, i);
for (int i = 0; i < 10; i++)
System.out.print(gai.get(i) + " ");
System.out.println();
try {
Integer[] ia = gai.rep();
} catch (Exception e) {
System.out.println(e);
}
}
}
/* Output:
0 1 2 3 4 5 6 7 8 9
java.lang.ClassCastException: [Ljava.lang.Object;
cannot be cast to [Ljava.lang.Integer;
*/
```
最初,看起来并没有太大不同,只是转换的位置移动了。没有 **@SuppressWarnings** 注解,仍然会收到“unchecked”警告。但是,内部表示现在是 `Object[]` 而不是 `T[]` 。 调用 `get()` 时,它将对象强制转换为 **T** ,实际上这是正确的类型,因此很安全。但是,如果调用 `rep()` ,它将再次尝试将 `Object[]` 强制转换为 `T[]` ,但仍然不正确,并在编译时生成警告,并在运行时生成异常。因此,无法破坏基础数组的类型,该基础数组只能是 `Object[]` 。在内部将数组视为 `Object[]` 而不是 `T[]` 的优点是,我们不太可能会忘记数组的运行时类型并意外地引入了bug,尽管大多数(也许是全部)此类错误会在运行时被迅速检测到。
对于新代码,请传入类型标记。在这种情况下,**GenericArray** 如下所示:
```java
// generics/GenericArrayWithTypeToken.java
import java.lang.reflect.Array;
public class GenericArrayWithTypeToken<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArrayWithTypeToken(Class<T> type, int sz) {
array = (T[]) Array.newInstance(type, sz);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
// Expose the underlying representation:
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArrayWithTypeToken<Integer> gai =
new GenericArrayWithTypeToken<>(
Integer.class, 10);
// This now works:
Integer[] ia = gai.rep();
}
}
```
类型标记 **Class\<T\>** 被传递到构造函数中以从擦除中恢复,因此尽管必须使用 **@SuppressWarnings** 关闭来自强制类型转换的警告,但我们仍可以创建所需的实际数组类型。一旦获得了实际的类型,就可以返回它并产生所需的结果,如在主方法中看到的那样。数组的运行时类型是确切的类型 `T[]` 。
不幸的是,如果查看 Java 标准库中的源代码,你会发现到处都有从 **Object** 数组到参数化类型的转换。例如,这是**ArrayList** 中,复制一个 **Collection** 的构造函数,这里为了简化,去除了源码中对此不重要的代码:
```java
public ArrayList(Collection c) {
size = c.size();
elementData = (E[])new Object[size];
c.toArray(elementData);
}
```
如果你浏览 **ArrayList.java** 的代码,将会发现很多此类强制转换。当我们编译它时会发生什么?
```java
Note: ArrayList.java uses unchecked or unsafe operations
Note: Recompile with -Xlint:unchecked for details.
```
果然,标准库会产生很多警告。如果你使用过 C 语言,尤其是使用 ANSI C 之前的语言,你会记住警告的特殊效果:发现警告后,可以忽略它们。因此,除非程序员必须对其进行处理,否则最好不要从编译器发出任何类型的消息。
Neal Gafter(Java 5 的主要开发人员之一)在他的博客中[^2]指出,他在重写 Java 库时是很随意、马虎的,我们不应该像他那样做。Neal 还指出,他在不破坏现有接口的情况下无法修复某些 Java 库代码。因此,即使在 Java 库源代码中出现了一些习惯用法,它们也不一定是正确的做法。当查看库代码时,我们不能认为这就是要在自己代码中必须遵循的示例。
请注意,在 Java 文献中推荐使用类型标记技术,例如 Gilad Bracha 的论文《Generics in the Java Programming Language》[^3],他指出:“例如,这种用法已广泛用于新的 API 中以处理注解。” 我发现此技术在人们对于舒适度的看法方面存在一些不一致之处;有些人强烈喜欢本章前面介绍的工厂方法。
- 译者的话
- 前言
- 简介
- 第一章 对象的概念
- 抽象
- 接口
- 服务提供
- 封装
- 复用
- 继承
- "是一个"与"像是一个"的关系
- 多态
- 单继承结构
- 集合
- 对象创建与生命周期
- 异常处理
- 本章小结
- 第二章 安装Java和本书用例
- 编辑器
- Shell
- Java安装
- 校验安装
- 安装和运行代码示例
- 第三章 万物皆对象
- 对象操纵
- 对象创建
- 数据存储
- 基本类型的存储
- 高精度数值
- 数组的存储
- 代码注释
- 对象清理
- 作用域
- 对象作用域
- 类的创建
- 类型
- 字段
- 基本类型默认值
- 方法使用
- 返回类型
- 参数列表
- 程序编写
- 命名可见性
- 使用其他组件
- static关键字
- 小试牛刀
- 编译和运行
- 编码风格
- 本章小结
- 第四章 运算符
- 开始使用
- 优先级
- 赋值
- 方法调用中的别名现象
- 算术运算符
- 一元加减运算符
- 递增和递减
- 关系运算符
- 测试对象等价
- 逻辑运算符
- 短路
- 字面值常量
- 下划线
- 指数计数法
- 位运算符
- 移位运算符
- 三元运算符
- 字符串运算符
- 常见陷阱
- 类型转换
- 截断和舍入
- 类型提升
- Java没有sizeof
- 运算符总结
- 本章小结
- 第五章 控制流
- true和false
- if-else
- 迭代语句
- while
- do-while
- for
- 逗号操作符
- for-in 语法
- return
- break 和 continue
- 臭名昭著的 goto
- switch
- switch 字符串
- 本章小结
- 第六章 初始化和清理
- 利用构造器保证初始化
- 方法重载
- 区分重载方法
- 重载与基本类型
- 返回值的重载
- 无参构造器
- this关键字
- 在构造器中调用构造器
- static 的含义
- 垃圾回收器
- finalize()的用途
- 你必须实施清理
- 终结条件
- 垃圾回收器如何工作
- 成员初始化
- 指定初始化
- 构造器初始化
- 初始化的顺序
- 静态数据的初始化
- 显式的静态初始化
- 非静态实例初始化
- 数组初始化
- 动态数组创建
- 可变参数列表
- 枚举类型
- 本章小结
- 第七章 封装
- 包的概念
- 代码组织
- 创建独一无二的包名
- 冲突
- 定制工具库
- 使用 import 改变行为
- 使用包的忠告
- 访问权限修饰符
- 包访问权限
- public: 接口访问权限
- 默认包
- private: 你无法访问
- protected: 继承访问权限
- 包访问权限 Vs Public 构造器
- 接口和实现
- 类访问权限
- 本章小结
- 第八章 复用
- 组合语法
- 继承语法
- 初始化基类
- 带参数的构造函数
- 委托
- 结合组合与继承
- 保证适当的清理
- 名称隐藏
- 组合与继承的选择
- protected
- 向上转型
- 再论组合和继承
- final关键字
- final 数据
- 空白 final
- final 参数
- final 方法
- final 和 private
- final 类
- final 忠告
- 类初始化和加载
- 继承和初始化
- 本章小结
- 第九章 多态
- 向上转型回顾
- 忘掉对象类型
- 转机
- 方法调用绑定
- 产生正确的行为
- 可扩展性
- 陷阱:“重写”私有方法
- 陷阱:属性与静态方法
- 构造器和多态
- 构造器调用顺序
- 继承和清理
- 构造器内部多态方法的行为
- 协变返回类型
- 使用继承设计
- 替代 vs 扩展
- 向下转型与运行时类型信息
- 本章小结
- 第十章 接口
- 抽象类和方法
- 接口创建
- 默认方法
- 多继承
- 接口中的静态方法
- Instrument 作为接口
- 抽象类和接口
- 完全解耦
- 多接口结合
- 使用继承扩展接口
- 结合接口时的命名冲突
- 接口适配
- 接口字段
- 初始化接口中的字段
- 接口嵌套
- 接口和工厂方法模式
- 本章小结
- 第十一章 内部类
- 创建内部类
- 链接外部类
- 使用 .this 和 .new
- 内部类与向上转型
- 内部类方法和作用域
- 匿名内部类
- 嵌套类
- 接口内部的类
- 从多层嵌套类中访问外部类的成员
- 为什么需要内部类
- 闭包与回调
- 内部类与控制框架
- 继承内部类
- 内部类可以被覆盖么?
- 局部内部类
- 内部类标识符
- 本章小结
- 第十二章 集合
- 泛型和类型安全的集合
- 基本概念
- 添加元素组
- 集合的打印
- 迭代器Iterators
- ListIterator
- 链表LinkedList
- 堆栈Stack
- 集合Set
- 映射Map
- 队列Queue
- 优先级队列PriorityQueue
- 集合与迭代器
- for-in和迭代器
- 适配器方法惯用法
- 本章小结
- 简单集合分类
- 第十三章 函数式编程
- 新旧对比
- Lambda表达式
- 递归
- 方法引用
- Runnable接口
- 未绑定的方法引用
- 构造函数引用
- 函数式接口
- 多参数函数式接口
- 缺少基本类型的函数
- 高阶函数
- 闭包
- 作为闭包的内部类
- 函数组合
- 柯里化和部分求值
- 纯函数式编程
- 本章小结
- 第十四章 流式编程
- 流支持
- 流创建
- 随机数流
- int 类型的范围
- generate()
- iterate()
- 流的建造者模式
- Arrays
- 正则表达式
- 中间操作
- 跟踪和调试
- 流元素排序
- 移除元素
- 应用函数到元素
- 在map()中组合流
- Optional类
- 便利函数
- 创建 Optional
- Optional 对象操作
- Optional 流
- 终端操作
- 数组
- 集合
- 组合
- 匹配
- 查找
- 信息
- 数字流信息
- 本章小结
- 第十五章 异常
- 异常概念
- 基本异常
- 异常参数
- 异常捕获
- try 语句块
- 异常处理程序
- 终止与恢复
- 自定义异常
- 异常与记录日志
- 异常声明
- 捕获所有异常
- 多重捕获
- 栈轨迹
- 重新抛出异常
- 精准的重新抛出异常
- 异常链
- Java 标准异常
- 特例:RuntimeException
- 使用 finally 进行清理
- finally 用来做什么?
- 在 return 中使用 finally
- 缺憾:异常丢失
- 异常限制
- 构造器
- Try-With-Resources 用法
- 揭示细节
- 异常匹配
- 其他可选方式
- 历史
- 观点
- 把异常传递给控制台
- 把“被检查的异常”转换为“不检查的异常”
- 异常指南
- 本章小结
- 后记:Exception Bizarro World
- 第十六章 代码校验
- 测试
- 如果没有测试过,它就是不能工作的
- 单元测试
- JUnit
- 测试覆盖率的幻觉
- 前置条件
- 断言(Assertions)
- Java 断言语法
- Guava断言
- 使用断言进行契约式设计
- 检查指令
- 前置条件
- 后置条件
- 不变性
- 放松 DbC 检查或非严格的 DbC
- DbC + 单元测试
- 使用Guava前置条件
- 测试驱动开发
- 测试驱动 vs. 测试优先
- 日志
- 日志会给出正在运行的程序的各种信息
- 日志等级
- 调试
- 使用 JDB 调试
- 图形化调试器
- 基准测试
- 微基准测试
- JMH 的引入
- 剖析和优化
- 优化准则
- 风格检测
- 静态错误分析
- 代码重审
- 结对编程
- 重构
- 重构基石
- 持续集成
- 本章小结
- 第十七章 文件
- 文件和目录路径
- 选取路径部分片段
- 路径分析
- Paths的增减修改
- 目录
- 文件系统
- 路径监听
- 文件查找
- 文件读写
- 本章小结
- 第十八章 字符串
- 字符串的不可变
- +的重载与StringBuilder
- 意外递归
- 字符串操作
- 格式化输出
- printf()
- System.out.format()
- Formatter类
- 格式化修饰符
- Formatter转换
- String.format()
- 一个十六进制转储(dump)工具
- 正则表达式
- 基础
- 创建正则表达式
- 量词
- CharSequence
- Pattern和Matcher
- find()
- 组(Groups)
- start()和end()
- Pattern标记
- split()
- 替换操作
- 正则表达式与 Java I/O
- 扫描输入
- Scanner分隔符
- 用正则表达式扫描
- StringTokenizer类
- 本章小结
- 第十九章 类型信息
- 为什么需要 RTTI
- Class对象
- 类字面常量
- 泛化的Class引用
- cast()方法
- 类型转换检测
- 使用类字面量
- 递归计数
- 一个动态instanceof函数
- 注册工厂
- 类的等价比较
- 反射:运行时类信息
- 类方法提取器
- 动态代理
- Optional类
- 标记接口
- Mock 对象和桩
- 接口和类型
- 本章小结
- 第二十章 泛型
- 简单泛型
- 泛型接口
- 泛型方法
- 复杂模型构建
- 泛型擦除
- 补偿擦除
- 边界
- 通配符
- 问题
- 自限定的类型
- 动态类型安全
- 泛型异常
- 混型
- 潜在类型机制
- 对缺乏潜在类型机制的补偿
- Java8 中的辅助潜在类型
- 总结:类型转换真的如此之糟吗?
- 进阶阅读
- 第二十一章 数组
- 数组特性
- 一等对象
- 返回数组
- 多维数组
- 泛型数组
- Arrays的fill方法
- Arrays的setAll方法
- 增量生成
- 随机生成
- 泛型和基本数组
- 数组元素修改
- 数组并行
- Arrays工具类
- 数组比较
- 数组拷贝
- 流和数组
- 数组排序
- Arrays.sort()的使用
- 并行排序
- binarySearch二分查找
- parallelPrefix并行前缀
- 本章小结
- 第二十二章 枚举
- 基本 enum 特性
- 将静态类型导入用于 enum
- 方法添加
- 覆盖 enum 的方法
- switch 语句中的 enum
- values 方法的神秘之处
- 实现而非继承
- 随机选择
- 使用接口组织枚举
- 使用 EnumSet 替代 Flags
- 使用 EnumMap
- 常量特定方法
- 使用 enum 的职责链
- 使用 enum 的状态机
- 多路分发
- 使用 enum 分发
- 使用常量相关的方法
- 使用 EnumMap 进行分发
- 使用二维数组
- 本章小结
- 第二十三章 注解
- 基本语法
- 定义注解
- 元注解
- 编写注解处理器
- 注解元素
- 默认值限制
- 替代方案
- 注解不支持继承
- 实现处理器
- 使用javac处理注解
- 最简单的处理器
- 更复杂的处理器
- 基于注解的单元测试
- 在 @Unit 中使用泛型
- 实现 @Unit
- 本章小结
- 第二十四章 并发编程
- 术语问题
- 并发的新定义
- 并发的超能力
- 并发为速度而生
- 四句格言
- 1.不要这样做
- 2.没有什么是真的,一切可能都有问题
- 3.它起作用,并不意味着它没有问题
- 4.你必须仍然理解
- 残酷的真相
- 本章其余部分
- 并行流
- 创建和运行任务
- 终止耗时任务
- CompletableFuture类
- 基本用法
- 结合 CompletableFuture
- 模拟
- 异常
- 流异常(Stream Exception)
- 检查性异常
- 死锁
- 构造方法非线程安全
- 复杂性和代价
- 本章小结
- 缺点
- 共享内存陷阱
- This Albatross is Big
- 其他类库
- 考虑为并发设计的语言
- 拓展阅读
- 第二十五章 设计模式
- 概念
- 单例模式
- 模式分类
- 构建应用程序框架
- 面向实现
- 工厂模式
- 动态工厂
- 多态工厂
- 抽象工厂
- 函数对象
- 命令模式
- 策略模式
- 责任链模式
- 改变接口
- 适配器模式(Adapter)
- 外观模式(Façade)
- 包(Package)作为外观模式的变体
- 解释器:运行时的弹性
- 回调
- 多次调度
- 模式重构
- 抽象用法
- 多次派遣
- 访问者模式
- RTTI的优劣
- 本章小结
- 附录:补充
- 附录:编程指南
- 附录:文档注释
- 附录:对象传递和返回
- 附录:流式IO
- 输入流类型
- 输出流类型
- 添加属性和有用的接口
- 通过FilterInputStream 从 InputStream 读取
- 通过 FilterOutputStream 向 OutputStream 写入
- Reader和Writer
- 数据的来源和去处
- 更改流的行为
- 未发生改变的类
- RandomAccessFile类
- IO流典型用途
- 缓冲输入文件
- 从内存输入
- 格式化内存输入
- 基本文件的输出
- 文本文件输出快捷方式
- 存储和恢复数据
- 读写随机访问文件
- 本章小结
- 附录:标准IO
- 附录:新IO
- ByteBuffer
- 数据转换
- 基本类型获取
- 视图缓冲区
- 字节存储次序
- 缓冲区数据操作
- 缓冲区细节
- 内存映射文件
- 性能
- 文件锁定
- 映射文件的部分锁定
- 附录:理解equals和hashCode方法
- 附录:集合主题
- 附录:并发底层原理
- 附录:数据压缩
- 附录:对象序列化
- 附录:静态语言类型检查
- 附录:C++和Java的优良传统
- 附录:成为一名程序员