目錄
介紹
《On Java 8》是什麼?
它是《Thinking In Java》的作者Bruce Eckel基於Java8寫的新書。裏面包含了對Java深入的理解及思想維度的理念。可以比作Java界的“武學祕籍”。任何Java語言的使用者,甚至是非Java使用者但是對面向對象思想有興趣的程序員都該一讀的經典書籍。目前豆瓣評分9.5,是公認的編程經典。
爲什麼要寫這個系列的精讀博文?
由於書籍讀起來時間久,過程漫長,因此產生了寫本精讀系列的最初想法。除此之外,由於中文版是譯版,讀起來還是有較大的生硬感(這種差異並非譯者的翻譯問題,類似英文無法譯出唐詩的原因),這導致我們理解作者意圖需要一點推敲。再加上原書的內容很長,只第一章就多達一萬多字(不含代碼),讀起來就需要大量時間。
所以,如果現在有一個人能替我們先仔細讀一遍,篩選出其中的精華,讓我們可以在地鐵上或者路上不用花太多時間就可以瞭解這邊經典書籍的思想那就最好不過了。於是這個系列誕生了。
一些建議
推薦讀本書的英文版原著。此外,也可以參考本書的中文譯版。我在寫這個系列的時候,會盡量的保證以“陳述”的方式表達原著的內容,也會寫出自己的部分觀點,但是這種觀點會保持理性並儘量少而精。本系列中對於原著的內容會以引用的方式體現。
最重要的一點,大家可以通過博客平臺的評論功能多加交流,這也是學習的一個重要環節。
第十三章 函數式編程
本章總字數:12800
關鍵詞:
- Lambda
- 方法引用
- 高階函數
本篇是系列《On Java 8》精讀的第六篇。相比較前幾篇,本篇更多講解編程的方法及技巧。在原著中,十三至十六章多以編程經驗理論爲主。但是文中依然有不少值得學習的知識,比如——Lambda。
比起C#,Java支持Lambda較晚——在Java8中新增了該功能。在前幾篇講Java內部類的時候我們曾經提到過Lambda,作者也同樣認爲Lambda的出現使得內部類的一些寫法被取代。
Lambda
首先,Lambda表達式所產生的是函數(方法)而不是類。
public interface Print {
String outPrint();
}
void test() {
Print print = () -> "hello!";
System.out.println(print.outPrint());
}
結果:
hello!
Lambda表達式由 ->符號及參數、方法體組成。 ->符號前是參數部分,之後是方法體。
上文示例是沒有參數的情況,也可以寫出如下代碼:
當有一個參數時:
public interface Print {
String outPrint(String s);
}
void test() {
Print print = (s) -> "Hi "+s;
System.out.println(print.outPrint("Jimmy!"));
}
結果:
Hi Jimmy!
當有多個參數時:
public interface Print {
String outPrint(String s, int l);
}
void test() {
Print print = (s,l) -> "Hi "+s.substring(0,l);
System.out.println(print.outPrint("Jimmy!!!!!!!!!!!!!!!!!",6));
}
結果:
Hi Jimmy!
[1] 當只用一個參數,可以不需要括號 ()。 然而,這是一個特例。
[2] 正常情況使用括號 () 包裹參數。 爲了保持一致性,也可以使用括號 () 包裹單個參數,雖然這種情況並不常見。
[3] 如果沒有參數,則必須使用括號 () 表示空參數列表。
[4] 對於多個參數,將參數列表放在括號 () 中。
方法引用
除了Lambda,方法引用也是一個很有用的編程技巧。但事實上,它在實戰中很少被用到,大家對它的瞭解也少於lambda。
方法引用使用 ::符號, ::符號前是類名或對象名稱,符號後是引用的方法名。
public interface Print {
void outPrint(String s);
}
public class PrintHi {
void outPrint(String s) {
System.out.println("Hi " + s);
}
}
@Test
void test() {
PrintHi pH = new PrintHi();
Print p = pH::outPrint;
p.outPrint("Jimmy!");
}
結果:
Hi Jimmy!
除了使用對象方式,也可以引用static方法。由於是靜態方法,直接使用類名即可,不需要實例化。
public interface Print {
void outPrint(String s);
}
public static class PrintHi {
static void outPrint(String s) {
System.out.println("Hi " + s);
}
}
@Test
void test() {
Print p = PrintHi::outPrint;
p.outPrint("Jimmy!");
}
結果:
Hi Jimmy!
高階函數
高階函數是一個產生或消費函數(方法)的函數(方法)。
import java.util.function.Function;
...
static Function<String, String> outPrint() {
return s -> s + " Jimmy!!";
}
@Test
void test() {
Function<String, String> f = outPrint();
System.out.println(f.apply("Hi"));
}
結果:
Hi Jimmy!!
高階函數沒有聽起來那麼嚇人。上個示例中outPrint() 內部提供了一個方法,這個方法在後面通過apply() 執行。
值得一提的是, Function<String, String> 中,第一個String是參數,第二個是返回值。如果要使用Function,需要引入java.util.function.Function 接口。
上文示例,高階函數是作爲函數生產者的身份,接下來我們試試讓他作爲消費者:
import java.util.function.Function;
...
static String outPrint(Function<String, String> method) {
return method.apply("Jimmy!!!");
}
@Test
void test() {
System.out.println(outPrint((s) -> "Hi " + s));
}
結果:
Hi Jimmy!!!
第十四章 流式編程
本章總字數:17000
關鍵詞:
- 什麼是流式編程
- 流創建
- 中間操作
- 終端操作
什麼是流式編程
流式編程是Java8新加入的編程概念。提起它,我們更容易想起另外一種編程語言——SQL。SQL的編程方式很有特點,往往你只需要告訴編譯器你想要什麼就可以了。比如查詢A表,直接就是:
select * from A
具體A表是怎麼查的,是怎麼從數據庫遍歷取出的,每個數據類型是怎麼處理的,我們都不需要關注。“我就要A的數據”——就這麼簡單。
流式編程的特點與之類似。我們對一個數據集合進行處理而不需要考慮操作細節,這就是流式編程。
先看一則原著中的示例:
// streams/Randoms.java
import java.util.*;
public class Randoms {
public static void main(String[] args) {
new Random(47)
.ints(5, 20)
.distinct()
.limit(7)
.sorted()
.forEach(System.out::println);
}
}
結果:
6
10
13
16
17
18
19
我們給 Random 對象一個種子(以便程序再次運行時產生相同的輸出)。ints() 方法產生一個流並且 ints() 方法有多種方式的重載 — 兩個參數限定了數值產生的邊界。這將生成一個整數流。我們可以使用中間流操作(intermediate stream operation) distinct() 來獲取它們的非重複值,然後使用 limit() 方法獲取前 7 個元素。接下來,我們使用 sorted() 方法排序。最終使用 forEach() 方法遍歷輸出,它根據傳遞給它的函數對每個流對象執行操作。在這裏,我們傳遞了一個可以在控制檯顯示每個元素的方法引用。System.out::println 。
流創建
除了上個示例中的Random (隨機數流),在Java8中我們還可以使用很多其他類型的流。
比如,IntStream (整數流):
// streams/Ranges.java
import static java.util.stream.IntStream.*;
public class Ranges {
public static void main(String[] args) {
// 傳統方法:
int result = 0;
for (int i = 10; i < 20; i++)
result += i;
System.out.println(result);
// for-in 循環:
result = 0;
for (int i : range(10, 20).toArray())
result += i;
System.out.println(result);
// 使用流:
System.out.println(range(10, 20).sum());
}
}
結果:
145
145
145
以及 Arrays.stream (數組流):
// streams/ArrayStreams.java
import java.util.*;
import java.util.stream.*;
public class ArrayStreams {
public static void main(String[] args) {
Arrays.stream(new double[] { 3.14159, 2.718, 1.618 })
.forEach(n -> System.out.format("%f ", n));
System.out.println();
Arrays.stream(new int[] { 1, 3, 5 })
.forEach(n -> System.out.format("%d ", n));
System.out.println();
Arrays.stream(new long[] { 11, 22, 44, 66 })
.forEach(n -> System.out.format("%d ", n));
System.out.println();
// 選擇一個子域:
Arrays.stream(new int[] { 1, 3, 5, 7, 15, 28, 37 }, 3, 6)
.forEach(n -> System.out.format("%d ", n));
}
}
結果:
3.141590 2.718000 1.618000
1 3 5
11 22 44 66
7 15 28
最後一次 stream() 的調用有兩個額外的參數。第一個參數告訴 stream() 從數組的哪個位置開始選擇元素,第二個參數用於告知在哪裏停止。每種不同類型的 stream() 都有類似的操作。
中間操作
流對象在創建之後要進行一系列操作,而這些操作流的中間操作會在處理之後將對象作爲一個新流輸出。
可能有點繞口。舉個簡單的例子就明白了:
select * from A order by a
這個SQL語句前半部分只是查出數據,而後半部分對數據進行排序,排序之後的對象作爲一個新數據流輸出,就是我們最終看到的排過序的A表數據。
在本章第一個例子中我們就已經見過幾箇中間操作,比如:sorted() 。
sorted() 除了使用無參的形式默認排序,還可以傳入Comparator參數,或者傳入Lambda表達式。
其他較常用的中間操作:
- distinct():用於消除流中的重複元素。
- filter(Predicate):過濾操作會保留與傳遞進去的過濾器函數計算結果爲 true 元素。
- map(Function):將函數操作應用在輸入流的元素中,並將返回值傳遞到輸出流中。
- mapToInt(ToIntFunction):操作同上,但結果是 IntStream。
- mapToLong(ToLongFunction):操作同上,但結果是 LongStream。
- mapToDouble(ToDoubleFunction):操作同上,但結果是 DoubleStream。
- sorted():元素排序。
終端操作
終端操作可以認爲是流處理的最後一步——將流轉換爲對象。
常用的終端操作,引用原著內容:
- toArray():將流轉換成適當類型的數組。
- toArray(generator):在特殊情況下,生成自定義類型的數組。
- forEach(Consumer)常見如 System.out::println 作爲 Consumer 函數。
- forEachOrdered(Consumer): 保證 forEach 按照原始流順序操作。
- collect(Collector):使用 Collector 收集流元素到結果集合中。
- collect(Supplier, BiConsumer, BiConsumer):同上,第一個參數 Supplier 創建了一個新結果集合,第二個參數 BiConsumer 將下一個元素包含到結果中,第三個參數 BiConsumer 用於將兩個值組合起來。
- allMatch(Predicate) :如果流的每個元素根據提供的 Predicate 都返回 true 時,結果返回爲 true。在 - 第一個 false 時,則停止執行計算。
- anyMatch(Predicate):如果流中的任意一個元素根據提供的 Predicate 返回 true 時,結果返回爲 true。在第一個 false 是停止執行計算。
- noneMatch(Predicate):如果流的每個元素根據提供的 Predicate 都返回 false 時,結果返回爲 true。在第一個 true 時停止執行計算。
- findFirst():返回第一個流元素的 Optional,如果流爲空返回 Optional.empty。
- findAny(:返回含有任意流元素的 Optional,如果流爲空返回 Optional.empty。
- count():流中的元素個數。
- max(Comparator):根據所傳入的 Comparator 所決定的“最大”元素。
- min(Comparator):根據所傳入的 Comparator 所決定的“最小”元素。
- average() :求取流元素平均值。
第十五章 異常
本章總字數:26800
關鍵詞:
- 異常的概念
- 異常捕獲
- 自定義異常
- 捕獲所有異常
- finally清理
異常的概念
人總會犯錯,程序也是。任何未知的因素都可能導致發生異常。有些異常比較容易捕捉,甚至我們自己就能看出來,比如:1/0,因爲除數不能爲0會導致異常;有些異常則難以預料,這時候就需要對可能出現的異常預先設置捕獲。
作者提了一個例子:
if(t == null)
throw new NullPointerException();
如果對象t 是 null,而被直接使用,就會出錯,所以進行一個if 判斷,如果爲null 則拋出一個NullPointerException 異常。
異常也可以帶參數:
throw new NullPointerException("t = null");
關鍵字 throw 將產生許多有趣的結果。在使用 new 創建了異常對象之後,此對象的引用將傳給 throw。儘管異常對象的類型通常與方法設計的返回類型不同,但從效果上看,它就像是從方法“返回”的。可以簡單地把異常處理看成一種不同的返回機制,當然若過分強調這種類比的話,就會有麻煩了。另外還能用拋出異常的方式從當前的作用域退出。在這兩種情況下,將會返回一個異常對象,然後退出方法或作用域。
拋出異常與方法正常返回的相似之處到此爲止。因爲異常返回的“地點”與普通方法調用返回的“地點”完全不同。(異常將在一個恰當的異常處理程序中得到解決,它的位置可能離異常被拋出的地方很遠,也可能會跨越方法調用棧的許多層級。)
異常捕獲
如果需要針對某一個代碼塊進行異常捕獲,可以使用try 關鍵詞,對指定異常進行的特殊處理則可以用 catch關鍵詞。
try {
// Code that might generate exceptions
} catch(Type1 id1) {
// Handle exceptions of Type1
} catch(Type2 id2) {
// Handle exceptions of Type2
} catch(Type3 id3) {
// Handle exceptions of Type3
}
// etc.
異常處理程序必須緊跟在 try 塊之後。當異常被拋出時,異常處理機制將負責搜尋參數與異常類型相匹配的第一個處理程序。然後進入 catch 子句執行,此時認爲異常得到了處理。一旦 catch 子句結束,則處理程序的查找過程結束
自定義異常
在Java中可以自定義異常類型。針對不同的使用場景,可以使用繼承的方式創建新的異常。
比如:
class SimpleException extends Exception {}
我們也可以創造出接受參數的自定義異常:
// exceptions/FullConstructors.java
class MyException extends Exception {
MyException() {}
MyException(String msg) { super(msg); }
}
public class FullConstructors {
public static void f() throws MyException {
System.out.println("Throwing MyException from f()");
throw new MyException();
}
public static void g() throws MyException {
System.out.println("Throwing MyException from g()");
throw new MyException("Originated in g()");
}
public static void main(String[] args) {
try {
f();
} catch(MyException e) {
e.printStackTrace(System.out);
}
try {
g();
} catch(MyException e) {
e.printStackTrace(System.out);
}
}
}
結果:
Throwing MyException from f()
MyException
at FullConstructors.f(FullConstructors.java:11)
at
FullConstructors.main(FullConstructors.java:19)
Throwing MyException from g()
MyException: Originated in g()
at FullConstructors.g(FullConstructors.java:15)
at
FullConstructors.main(FullConstructors.java:24)
方法聲明處顯式的列出異常,可以讓使用的程序員知道需要做哪些異常處理:
void f() throws TooBig, TooSmall, DivZero { // ...
Java 鼓勵人們把方法可能會拋出的異常告知使用此方法的客戶端程序員。這是種優雅的做法,它使得調用者能確切知道寫什麼樣的代碼可以捕獲所有潛在的異常。
捕獲所有異常
Exception 是所有異常類的基類,也因此可以使用它來捕獲所有異常。
catch(Exception e) {
System.out.println("Caught an exception");
}
對於捕獲的異常,我們可以使用其自帶的方法來獲取關於異常的信息:
// exceptions/ExceptionMethods.java
// Demonstrating the Exception Methods
public class ExceptionMethods {
public static void main(String[] args) {
try {
throw new Exception("My Exception");
} catch(Exception e) {
System.out.println("Caught Exception");
System.out.println(
"getMessage():" + e.getMessage());
System.out.println("getLocalizedMessage():" +
e.getLocalizedMessage());
System.out.println("toString():" + e);
System.out.println("printStackTrace():");
e.printStackTrace(System.out);
}
}
}
結果:
Caught Exception
getMessage():My Exception
getLocalizedMessage():My Exception
toString():java.lang.Exception: My Exception
printStackTrace():
java.lang.Exception: My Exception
at
ExceptionMethods.main(ExceptionMethods.java:7)
當有多個異常捕獲且作出統一處理時,可以使用| 符號:
// exceptions/MultiCatch.java
public class MultiCatch {
void x() throws Except1, Except2, Except3, Except4 {}
void process() {}
void f() {
try {
x();
} catch(Except1 | Except2 | Except3 | Except4 e) {
process();
}
}
}
通過 throw 關鍵詞可以已經捕獲的異常重新拋出。
catch(Exception e) {
throw e;
}
重拋異常會把異常拋給上一級環境中的異常處理程序,同一個 try 塊的後續 catch 子句將被忽略。此外,異常對象的所有信息都得以保持,所以高一級環境中捕獲此異常的處理程序可以從這個異常對象中得到所有信息。
finally清理
在一個代碼塊中,如果我們希望無論是否有異常發生都執行某一個固定的操作,就可以使用finally 關鍵詞。
比如:
try {
// The guarded region: Dangerous activities
// that might throw A, B, or C
} catch(A a1) {
// Handler for situation A
} catch(B b1) {
// Handler for situation B
} catch(C c1) {
// Handler for situation C
} finally {
// Activities that happen every time
}
那什麼時候需要用到finally 關鍵詞呢?有以下幾個場景:
- 操作外部文件或應用程序,在最終需要關閉時使用。
- 對圖形界面的繪製,在最終需要清除對象時使用。
- 在打開網絡連接,最終需要斷開時使用。
- 其他各種需要釋放的對象或需要重置的內容。
finally 子句在任何時候都會被執行。即便在與 return關鍵詞同在一個代碼塊中時,finally 依然會被執行。
// exceptions/MultipleReturns.java
public class MultipleReturns {
public static void f(int i) {
System.out.println(
"Initialization that requires cleanup");
try {
System.out.println("Point 1");
if(i == 1) return;
System.out.println("Point 2");
if(i == 2) return;
System.out.println("Point 3");
if(i == 3) return;
System.out.println("End");
return;
} finally {
System.out.println("Performing cleanup");
}
}
public static void main(String[] args) {
for(int i = 1; i <= 4; i++)
f(i);
}
}
結果:
Initialization that requires cleanup
Point 1
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
End
Performing cleanup
如果使用finally 不恰當會出現異常丟失的現象。
比如這個示例中,即便出現了異常也不會被處理。
// exceptions/ExceptionSilencer.java
public class ExceptionSilencer {
public static void main(String[] args) {
try {
throw new RuntimeException();
} finally {
// Using 'return' inside the finally block
// will silence any thrown exception.
return;
}
}
}
第十六章 代碼校驗
本章總字數:22800
關鍵詞:
- 單元測試
- JUnit
- 斷言
上一章我們講到了異常,也說過一句話“人總會犯錯,程序也是”。這句話反過來還有另外一個含義——“程序會犯錯,人也是”。
如果說異常捕獲是可以捕獲未知的程序錯誤,那程序測試就是針對人犯錯的良藥。
單元測試與JUnit
“單元”是指測試一小部分代碼 。通常,每個類都有測試來檢查它所有方法的行爲。“系統”測試則是不同的,它檢查的是整個程序是否滿足要求。
JUnit是一個Java語言的單元測試框架。目前最新的版本是JUnit5。在Java8中,JUnit甚至支持Lambda表達式。
原著中的示例:
創建 CountedList 類,並繼承 ArrayList:
// validating/CountedList.java
// Keeps track of how many of itself are created.
package validating;
import java.util.*;
public class CountedList extends ArrayList<String> {
private static int counter = 0;
private int id = counter++;
public CountedList() {
System.out.println("CountedList #" + id);
}
public int getId() { return id; }
}
創建一個測試類,來測試 CountedList 類。
// validating/tests/CountedListTest.java
// Simple use of JUnit to test CountedList.
package validating;
import java.util.*;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
public class CountedListTest {
private CountedList list;
@BeforeAll
static void beforeAllMsg() {
System.out.println(">>> Starting CountedListTest");
}
@AfterAll
static void afterAllMsg() {
System.out.println(">>> Finished CountedListTest");
}
@BeforeEach
public void initialize() {
list = new CountedList();
System.out.println("Set up for " + list.getId());
for(int i = 0; i < 3; i++)
list.add(Integer.toString(i));
}
@AfterEach
public void cleanup() {
System.out.println("Cleaning up " + list.getId());
}
@Test
public void insert() {
System.out.println("Running testInsert()");
assertEquals(list.size(), 3);
list.add(1, "Insert");
assertEquals(list.size(), 4);
assertEquals(list.get(1), "Insert");
}
@Test
public void replace() {
System.out.println("Running testReplace()");
assertEquals(list.size(), 3);
list.set(1, "Replace");
assertEquals(list.size(), 3);
assertEquals(list.get(1), "Replace");
}
// A helper method to simplify the code. As
// long as it's not annotated with @Test, it will
// not be automatically executed by JUnit.
private void compare(List<String> lst, String[] strs) {
assertArrayEquals(lst.toArray(new String[0]), strs);
}
@Test
public void order() {
System.out.println("Running testOrder()");
compare(list, new String[] { "0", "1", "2" });
}
@Test
public void remove() {
System.out.println("Running testRemove()");
assertEquals(list.size(), 3);
list.remove(1);
assertEquals(list.size(), 2);
compare(list, new String[] { "0", "2" });
}
@Test
public void addAll() {
System.out.println("Running testAddAll()");
list.addAll(Arrays.asList(new String[] {
"An", "African", "Swallow"}));
assertEquals(list.size(), 6);
compare(list, new String[] { "0", "1", "2",
"An", "African", "Swallow" });
}
}
結果:
>>> Starting CountedListTest
CountedList #0
Set up for 0
Running testRemove()
Cleaning up 0
CountedList #1
Set up for 1
Running testReplace()
Cleaning up 1
CountedList #2
Set up for 2
Running testAddAll()
Cleaning up 2
CountedList #3
Set up for 3
Running testInsert()
Cleaning up 3
CountedList #4
Set up for 4
Running testOrder()
Cleaning up 4
>>> Finished CountedListTest
- @BeforeAll 註解是在任何其他測試操作之前運行一次的方法。必須是靜態的。
- @AfterAll 是所有其他測試操作之後只運行一次的方法。必須是靜態的。
- @BeforeEach註解是通常用於創建和初始化公共對象的方法,並在每次測試前運行。
- @AfterEach,測試後執行清理(如果修改了需要恢復的靜態文件,打開文件需要關閉,打開數據庫或者網絡連接,etc)。
- @Test使JUnit發現需要測試的方法,並將每個方法作爲測試運行。
斷言
驗證一段程序是否滿足某個條件,這就是——斷言。
常用的斷言方式:
assert boolean-expression: information-expression;
原著示例:
public class Assert2 {
public static void main(String[] args) {
assert false:
"Here's a message saying what happened";
}
}
結果:
___[ Error Output ]___
Exception in thread "main" java.lang.AssertionError:
Here's a message saying what happened
at Assert2.main(Assert2.java:8)
斷言默認是關閉的,如果要啓用則需要:
ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true);
斷言的啓用,還需要分包開啓與類開啓。
因爲啓用 Java 本地斷言很麻煩,Guava 團隊添加一個始終啓用的用來替換斷言的 Verify 類。他們建議靜態導入 Verify 方法。
示例:
// validating/GuavaAssertions.java
// Assertions that are always enabled.
import com.google.common.base.*;
import static com.google.common.base.Verify.*;
public class GuavaAssertions {
public static void main(String[] args) {
verify(2 + 2 == 4);
try {
verify(1 + 2 == 4);
} catch(VerifyException e) {
System.out.println(e);
}
try {
verify(1 + 2 == 4, "Bad math");
} catch(VerifyException e) {
System.out.println(e.getMessage());
}
try {
verify(1 + 2 == 4, "Bad math: %s", "not 4");
} catch(VerifyException e) {
System.out.println(e.getMessage());
}
String s = "";
s = verifyNotNull(s);
s = null;
try {
verifyNotNull(s);
} catch(VerifyException e) {
System.out.println(e.getMessage());
}
try {
verifyNotNull(
s, "Shouldn't be null: %s", "arg s");
} catch(VerifyException e) {
System.out.println(e.getMessage());
}
}
}
結果:
com.google.common.base.VerifyException
Bad math
Bad math: not 4
expected a non-null reference
Shouldn't be null: arg s
提高代碼質量
原著中本章後面的內容主要講解使用日誌及JDB調試來檢驗代碼。但是在實際的項目開發中,我們往往有更強大的IDE輔助我們進行調試和代碼追蹤。所以這裏不再詳述。
在最後,作者提到了重構及結對編程對檢驗代碼的重要性。重構可以使代碼更加健壯,而結對編程可以review彼此的代碼同樣可以提高代碼質量。這些都是很好的建議。
總結
本篇共四章內容,主要講解了函數式編程、流式編程及異常處理和代碼測試相關的知識。本篇內容主要以編程技巧的講解爲主。瞭解了本篇的內容可以使你在日常編程中的技巧得以提升。特別是Lambda及流式編程的技巧是Java8新加入的功能,可以多用一用就會發現它的強大。