<!-- Annotation-Based Unit Testing -->
## 基于注解的單元測試
單元測試是對類中每個方法提供一個或者多個測試的一種事件,其目的是為了有規律的測試一個類中每個部分是否具備正確的行為。在 Java 中,最著名的單元測試工具就是 **JUnit**。**JUnit** 4 版本已經包含了注解。在注解版本之前的 JUnit 一個最主要的問題是,為了啟動和運行 **JUnit** 測試,有大量的“儀式”需要標注。這種負擔已經減輕了一些,**但是**注解使得測試更接近“可以工作的最簡單的測試系統”。
在注解版本之前的 JUnit,你必須創建一個單獨的文件來保存單元測試。通過注解,我們可以將單元測試集成在需要被測試的類中,從而將單元測試的時間和麻煩降到了最低。這種方式有額外的好處,就是使得測試私有方法和公有方法變的一樣容易。
這個基于注解的測試框架叫做 **@Unit**。其最基本的測試形式,可能也是你使用的最多的一個注解是 **@Test**,我們使用 **@Test** 來標記測試方法。測試方法不帶參數,并返回 **boolean** 結果來說明測試方法成功或者失敗。你可以任意命名它的測試方法。同時 **@Unit** 測試方法可以是任意你喜歡的訪問修飾方法,包括 **private**。
要使用 **@Unit**,你必須導入 **onjava.atunit** 包,并且使用 **@Unit** 的測試標記為合適的方法和字段打上標簽(在接下來的例子中你會學到),然后讓你的構建系統對編譯后的類運行 **@Unit**,下面是一個簡單的例子:
```java
// annotations/AtUnitExample1.java
// {java onjava.atunit.AtUnit
// build/classes/main/annotations/AtUnitExample1.class}
package annotations;
import onjava.atunit.*;
import onjava.*;
public class AtUnitExample1 {
public String methodOne() {
return "This is methodOne";
}
public int methodTwo() {
System.out.println("This is methodTwo");
return 2;
}
@Test
boolean methodOneTest() {
return methodOne().equals("This is methodOne");
}
@Test
boolean m2() { return methodTwo() == 2; }
@Test
private boolean m3() { return true; }
// Shows output for failure:
@Test
boolean failureTest() { return false; }
@Test
boolean anotherDisappointment() {
return false;
}
}
```
輸出為:
```java
annotations.AtUnitExample1
. m3
. methodOneTest
. m2 This is methodTwo
. failureTest (failed)
. anotherDisappointment (failed)
(5 tests)
>>> 2 FAILURES <<<
annotations.AtUnitExample1: failureTest
annotations.AtUnitExample1: anotherDisappointment
```
使用 **@Unit** 進行測試的類必須定義在某個包中(即必須包括 **package** 聲明)。
**@Test** 注解被置于 `methodOneTest()`、 `m2()`、`m3()`、`failureTest()` 以及 `anotherDisappointment()` 方法之前,它們告訴 **@Unit** 方法作為單元測試來運行。同時 **@Test** 確保這些方法沒有任何參數并且返回值為 **boolean** 或者 **void**。當你填寫單元測試時,唯一需要做的就是決定測試是成功還是失敗,(對于返回值為 **boolean** 的方法)應該返回 **ture** 還是 **false**。
如果你熟悉 **JUnit**,你還將注意到 **@Unit** 輸出的信息更多。你會看到現在正在運行的測試的輸出更有用,最后它會告訴你導致失敗的類和測試。
你并非必須將測試方法嵌入到原來的類中,有時候這種事情根本做不到。要生產一個非嵌入式的測試,最簡單的方式就是繼承:
```java
// annotations/AUExternalTest.java
// Creating non-embedded tests
// {java onjava.atunit.AtUnit
// build/classes/main/annotations/AUExternalTest.class}
package annotations;
import onjava.atunit.*;
import onjava.*;
public class AUExternalTest extends AtUnitExample1 {
@Test
boolean _MethodOne() {
return methodOne().equals("This is methodOne");
}
@Test
boolean _MethodTwo() {
return methodTwo() == 2;
}
}
```
輸出為:
```java
annotations.AUExternalTest
. tMethodOne
. tMethodTwo This is methodTwo
OK (2 tests)
```
這個示例還表現出靈活命名的價值。在這里,**@Test** 方法被命名為下劃線前綴加上要測試的方法名稱(我并不認為這是一種理想的命名形式,這只是表現一種可能性罷了)。
你也可以使用組合來創建非嵌入式的測試:
```java
// annotations/AUComposition.java
// Creating non-embedded tests
// {java onjava.atunit.AtUnit
// build/classes/main/annotations/AUComposition.class}
package annotations;
import onjava.atunit.*;
import onjava.*;
public class AUComposition {
AtUnitExample1 testObject = new AtUnitExample1();
@Test
boolean tMethodOne() {
return testObject.methodOne()
.equals("This is methodOne");
}
@Test
boolean tMethodTwo() {
return testObject.methodTwo() == 2;
}
}
```
輸出為:
```java
annotations.AUComposition
. tMethodTwo This is methodTwo
. tMethodOne
OK (2 tests)
```
因為在每一個測試里面都會創建 **AUComposition** 對象,所以創建新的成員變量 **testObject** 用于以后的每一個測試方法。
因為 **@Unit** 中沒有 **JUnit** 中特殊的 **assert** 方法,不過另一種形式的 **@Test** 方法仍然允許返回值為 **void**(如果你還想使用 **true** 或者 **false** 的話,也可以使用 **boolean** 作為方法返回值類型)。為了表示測試成功,可以使用 Java 的 **assert** 語句。Java 斷言機制需要你在 java 命令行行加上 **-ea** 標志來開啟,但是 **@Unit** 已經自動開啟了該功能。要表示測試失敗的話,你甚至可以使用異常。**@Unit** 的設計目標之一就是盡可能減少添加額外的語法,而 Java 的 **assert** 和異常對于報告錯誤而言,即已經足夠了。一個失敗的 **assert** 或者從方法從拋出的異常都被視為測試失敗,但是 **@Unit** 不會在這個失敗的測試上卡住,它會繼續運行,直到所有測試完畢,下面是一個示例程序:
```java
// annotations/AtUnitExample2.java
// Assertions and exceptions can be used in @Tests
// {java onjava.atunit.AtUnit
// build/classes/main/annotations/AtUnitExample2.class}
package annotations;
import java.io.*;
import onjava.atunit.*;
import onjava.*;
public class AtUnitExample2 {
public String methodOne() {
return "This is methodOne";
}
public int methodTwo() {
System.out.println("This is methodTwo");
return 2;
}
@Test
void assertExample() {
assert methodOne().equals("This is methodOne");
}
@Test
void assertFailureExample() {
assert 1 == 2: "What a surprise!";
}
@Test
void exceptionExample() throws IOException {
try(FileInputStream fis =
new FileInputStream("nofile.txt")) {} // Throws
}
@Test
boolean assertAndReturn() {
// Assertion with message:
assert methodTwo() == 2: "methodTwo must equal 2";
return methodOne().equals("This is methodOne");
}
}
```
輸出為:
```java
annotations.AtUnitExample2
. exceptionExample java.io.FileNotFoundException:
nofile.txt (The system cannot find the file specified)
(failed)
. assertExample
. assertAndReturn This is methodTwo
. assertFailureExample java.lang.AssertionError: What
a surprise!
(failed)
(4 tests)
>>> 2 FAILURES <<<
annotations.AtUnitExample2: exceptionExample
annotations.AtUnitExample2: assertFailureExample
```
如下是一個使用非嵌入式測試的例子,并且使用了斷言,它將會對 **java.util.HashSet** 進行一些簡單的測試:
```java
// annotations/HashSetTest.java
// {java onjava.atunit.AtUnit
// build/classes/main/annotations/HashSetTest.class}
package annotations;
import java.util.*;
import onjava.atunit.*;
import onjava.*;
public class HashSetTest {
HashSet<String> testObject = new HashSet<>();
@Test
void initialization() {
assert testObject.isEmpty();
}
@Test
void _Contains() {
testObject.add("one");
assert testObject.contains("one");
}
@Test
void _Remove() {
testObject.add("one");
testObject.remove("one");
assert testObject.isEmpty();
}
}
```
采用繼承的方式可能會更簡單,也沒有一些其他的約束。
對每一個單元測試而言,**@Unit** 都會使用默認的無參構造器,為該測試類所屬的類創建出一個新的實例。并在此新創建的對象上運行測試,然后丟棄該對象,以免對其他測試產生副作用。如此創建對象導致我們依賴于類的默認構造器。如果你的類沒有默認構造器,或者對象需要復雜的構造過程,那么你可以創建一個 **static** 方法專門負責構造對象,然后使用 **@TestObjectCreate** 注解標記該方法,例子如下:
```java
// annotations/AtUnitExample3.java
// {java onjava.atunit.AtUnit
// build/classes/main/annotations/AtUnitExample3.class}
package annotations;
import onjava.atunit.*;
import onjava.*;
public class AtUnitExample3 {
private int n;
public AtUnitExample3(int n) { this.n = n; }
public int getN() { return n; }
public String methodOne() {
return "This is methodOne";
}
public int methodTwo() {
System.out.println("This is methodTwo");
return 2;
}
@TestObjectCreate
static AtUnitExample3 create() {
return new AtUnitExample3(47);
}
@Test
boolean initialization() { return n == 47; }
@Test
boolean methodOneTest() {
return methodOne().equals("This is methodOne");
}
@Test
boolean m2() { return methodTwo() == 2; }
}
```
輸出為:
```java
annotations.AtUnitExample3
. initialization
. m2 This is methodTwo
. methodOneTest
OK (3 tests)
```
**@TestObjectCreate** 修飾的方法必須聲明為 **static** ,且必須返回一個你正在測試的類型對象,這一切都由 **@Unit** 負責確保成立。
有的時候,你需要向單元測試中增加一些字段。這時候可以使用 **@TestProperty** 注解,由它注解的字段表示只在單元測試中使用(因此,在你將產品發布給客戶之前,他們應該被刪除)。在下面的例子中,一個 **String** 通過 `String.split()` 方法進行分割,從其中讀取一個值,這個值將會被生成測試對象:
```java
// annotations/AtUnitExample4.java
// {java onjava.atunit.AtUnit
// build/classes/main/annotations/AtUnitExample4.class}
// {VisuallyInspectOutput}
package annotations;
import java.util.*;
import onjava.atunit.*;
import onjava.*;
public class AtUnitExample4 {
static String theory = "All brontosauruses " +
"are thin at one end, much MUCH thicker in the " +
"middle, and then thin again at the far end.";
private String word;
private Random rand = new Random(); // Time-based seed
public AtUnitExample4(String word) {
this.word = word;
}
public String getWord() { return word; }
public String scrambleWord() {
List<Character> chars = Arrays.asList(
ConvertTo.boxed(word.toCharArray()));
Collections.shuffle(chars, rand);
StringBuilder result = new StringBuilder();
for(char ch : chars)
result.append(ch);
return result.toString();
}
@TestProperty
static List<String> input =
Arrays.asList(theory.split(" "));
@TestProperty
static Iterator<String> words = input.iterator();
@TestObjectCreate
static AtUnitExample4 create() {
if(words.hasNext())
return new AtUnitExample4(words.next());
else
return null;
}
@Test
boolean words() {
System.out.println("'" + getWord() + "'");
return getWord().equals("are");
}
@Test
boolean scramble1() {
// Use specific seed to get verifiable results:
rand = new Random(47);
System.out.println("'" + getWord() + "'");
String scrambled = scrambleWord();
System.out.println(scrambled);
return scrambled.equals("lAl");
}
@Test
boolean scramble2() {
rand = new Random(74);
System.out.println("'" + getWord() + "'");
String scrambled = scrambleWord();
System.out.println(scrambled);
return scrambled.equals("tsaeborornussu");
}
}
```
輸出為:
```java
annotations.AtUnitExample4
. words 'All'
(failed)
. scramble1 'brontosauruses'
ntsaueorosurbs
(failed)
. scramble2 'are'
are
(failed)
(3 tests)
>>> 3 FAILURES <<<
annotations.AtUnitExample4: words
annotations.AtUnitExample4: scramble1
annotations.AtUnitExample4: scramble2
```
**@TestProperty** 也可以用來標記那些只在測試中使用的方法,但是它們本身不是測試方法。
如果你的測試對象需要執行某些初始化工作,并且使用完成之后還需要執行清理工作,那么可以選擇使用 **static** 的 **@TestObjectCleanup** 方法,當測試對象使用結束之后,該方法會為你執行清理工作。在下面的示例中,**@TestObjectCleanup** 為每一個測試對象都打開了一個文件,因此必須在丟棄測試的時候關閉該文件:
```java
// annotations/AtUnitExample5.java
// {java onjava.atunit.AtUnit
// build/classes/main/annotations/AtUnitExample5.class}
package annotations;
import java.io.*;
import onjava.atunit.*;
import onjava.*;
public class AtUnitExample5 {
private String text;
public AtUnitExample5(String text) {
this.text = text;
}
@Override
public String toString() { return text; }
@TestProperty
static PrintWriter output;
@TestProperty
static int counter;
@TestObjectCreate
static AtUnitExample5 create() {
String id = Integer.toString(counter++);
try {
output = new PrintWriter("Test" + id + ".txt");
} catch(IOException e) {
throw new RuntimeException(e);
}
return new AtUnitExample5(id);
}
@TestObjectCleanup
static void cleanup(AtUnitExample5 tobj) {
System.out.println("Running cleanup");
output.close();
}
@Test
boolean test1() {
output.print("test1");
return true;
}
@Test
boolean test2() {
output.print("test2");
return true;
}
@Test
boolean test3() {
output.print("test3");
return true;
}
}
```
輸出為:
```java
annotations.AtUnitExample5
. test1
Running cleanup
. test3
Running cleanup
. test2
Running cleanup
OK (3 tests)
```
在輸出中我們可以看到,清理方法會在每個測試方法結束之后自動運行。
- 譯者的話
- 前言
- 簡介
- 第一章 對象的概念
- 抽象
- 接口
- 服務提供
- 封裝
- 復用
- 繼承
- "是一個"與"像是一個"的關系
- 多態
- 單繼承結構
- 集合
- 對象創建與生命周期
- 異常處理
- 本章小結
- 第二章 安裝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的優良傳統
- 附錄:成為一名程序員