前些年 Scala 大肆流行,打出來(lái) Java 顛覆者的旗號(hào),究其底氣來(lái)源,無(wú)非是函數(shù)式和面向?qū)ο蟮摹巴昝澜Y(jié)合”,各式各樣的“語(yǔ)法糖”,但其過(guò)高的學(xué)習(xí)門檻,又給了新來(lái)者當(dāng)頭一棒。
隨著 Java8 的發(fā)布,Lambda 特性的引入,之前的焦灼局面是否有所轉(zhuǎn)變,讓我們一起揭開 Java 函數(shù)式編程的面紗:
- 面向?qū)ο?VS 函數(shù)式
- FunctionalInterface 和 Lambda
- 類庫(kù)的升級(jí)改造(默認(rèn)方法、靜態(tài)方法、Stream、Optional)
- Lambda 下模式的進(jìn)化
- Lambda 下并發(fā)程序
1. 面向?qū)ο?VS 函數(shù)式編程
一句話總結(jié)兩種的關(guān)系:面向?qū)ο缶幊淌菍?duì)數(shù)據(jù)進(jìn)行抽象;而函數(shù)式編程是對(duì)行為進(jìn)行抽象。
在現(xiàn)實(shí)世界中,數(shù)據(jù)和行為并存,程序也應(yīng)如此,可喜可賀的是在 Java 世界中,兩者也開啟了融合之旅。
首先思考一個(gè)問(wèn)題, 在 Java 編程中,我們?nèi)绾芜M(jìn)行行為傳遞,例如我們需要打印線程名稱和當(dāng)前時(shí)間,并將該任務(wù)提交到線程池中運(yùn)行,會(huì)有哪些方法?
方法 1:新建 class Task 實(shí)現(xiàn) Runnable 接口
public class Task implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms");
}
}
executorService.submit(new Task());
方法 2:匿名內(nèi)部類實(shí)現(xiàn) Runnable 接口
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms");
}
});
方法 3:使用 Lambda 表達(dá)式
executorService.submit(()-> System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms"));
方法 4:使用方法引用
private void print(){
System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms");
}
{
executorService.submit(this::print);
}
通過(guò)上面不同的行為傳遞方式,能夠比較直觀的體會(huì)到隨著函數(shù)式特性的引入,行為傳遞少了很多樣板代碼,增加了一絲靈活;可見Lambda表達(dá)式是一種緊湊的、傳遞行為的方式。
2. FunctionalInterface 和 Lambda
Java 函數(shù)式編程,只有兩個(gè)核心概念:
FunctionalInterface(函數(shù)接口)是只有一個(gè)抽象方法的接口,用作 Lambda 表達(dá)式的類型。
Lambda 表達(dá)式,及要傳遞的行為代碼,更像是一個(gè)匿名函數(shù)(當(dāng)然 java 中并沒(méi)有這個(gè)概念),將行為像數(shù)據(jù)那樣進(jìn)行傳遞。
換個(gè)好理解但是不正規(guī)的說(shuō)法,F(xiàn)unctionalInterface 為類型,Lambda 表達(dá)式為值;我們可以將一個(gè) Lambda 表達(dá)式賦予一個(gè)符合 FunctionalInterface 要求的接口變量(局部變量、方法參數(shù))。
2.1. Lambda 表達(dá)式
先看幾個(gè) Lambda 表達(dá)式的例子:
// 不包含參數(shù),用()表示沒(méi)有參數(shù)
// 表達(dá)式主體只有一個(gè)語(yǔ)句,可以省略{}
Runnable helloWord = () -> System.out.println("Hello World");
// 表達(dá)式主體由多個(gè)語(yǔ)句組成,不能省略{}
Runnable helloWords = () -> {
System.out.println("Hello");
System.out.println("Word");
System.out.println("Word");
};
// 表達(dá)式中只有一個(gè)參數(shù),可以省略()
Consumer<String> infoConsumer = msg -> System.out.println("Hello " + msg);
// 表達(dá)式由多個(gè)參數(shù)組成,不可省略()
BinaryOperator<Integer> add1 = (Integer i ,Integer j) -> i + j;
// 編譯器會(huì)進(jìn)行類型推斷,在沒(méi)有歧義情況下可以省略類型聲明,但是不可省略()
BinaryOperator<Integer> add2 = (i, j) -> i + j;
綜上可見,一個(gè) Lambda 表達(dá)式主要由三部分組成:
- 參數(shù)列表
- 箭頭分隔符(->)
- 主體,單個(gè)表達(dá)式或語(yǔ)句塊
我們?cè)谑褂媚涿麅?nèi)部類時(shí)有一些限制:引用方法中的變量時(shí),需要將變量聲明為 final,不能為其進(jìn)行重新賦值,如下:
final String msg = "World";
Runnable print = new Runnable() {
@Override
public void run() {
System.out.println("Hello" + msg);
}
};
在 Java8 中放松了這個(gè)限制,可以引用非 final 變量,但是該變量在既成事實(shí)上必須是 final 的,雖然無(wú)需將變量聲明為 final,在 Lambda 表達(dá)式中,也無(wú)法用作非最終態(tài)變量,及只能給該變量賦值一次(與用 final 聲明變量效果相同)。
2.2 FunctionalInterface
FunctionalInterface,只有一個(gè)抽象方法的接口就是函數(shù)式接口,接口中單一方法命名并不重要,只要方法簽名與 Lambda 表達(dá)式的類型匹配即可。
Java 內(nèi)置了常用函數(shù)接口如下:
1. Predicate<T>
參數(shù)類型:T
返回值:boolean
示例:Predicate<String> isAdmin = name -> "admin".equals(name);
2. Consumer<T>
參數(shù):T
返回值:void
示例:Consumer<String> print = msg -> System.out.println(msg);
3. Function<T,R>
參數(shù):T
返回值:R
示例:Function<Long, String> toStr = value -> String.valueOf(value);
4. Supplier<T>
參數(shù):none
返回值:T
示例:Supplier<Date> now = () -> new Date();
5. UnaryOperator<T>
參數(shù):T
返回值:T
示例:UnaryOperator<Boolean> negation = value -> !value.booleanValue();
6. BinaryOperator<T>
參數(shù):(T, T)
返回值:T
示例:BinaryOperator<Integer> intDouble = (i, j) -> i + j;
7. Runnable
參數(shù):none
返回值:void
示例:Runnable helloWord = () -> System.out.println("Hello World");
8. Callable<T>
參數(shù):nont
返回值:T
示例:Callable<Date> now1 = () -> new Date();
當(dāng)然我們也可以根據(jù)需求自定義函數(shù)接口,為了保證接口的有效性,可以在上面添加 @FunctionalInterface 注解,該注解會(huì)強(qiáng)制 javac 檢測(cè)一個(gè)接口是否符合函數(shù)式接口的規(guī)范,例如:
@FunctionalInterface
interface CustomFunctionalInterface{
void print(String msg);
}
CustomFunctionalInterface cfi= msg -> System.out.println(msg);
2.3 方法引用
Lambda 表達(dá)式一種常用方法便是直接調(diào)用其他方法,針對(duì)這種情況,Java8 提供了一個(gè)簡(jiǎn)寫語(yǔ)法,及方法引用,用于重用已有方法。
凡是可以使用 Lambda 表達(dá)式的地方,都可以使用方法引用。
方法應(yīng)用的標(biāo)準(zhǔn)語(yǔ)法為 ClassName::methodName,雖然這是一個(gè)方法,但不需要再后面加括號(hào),因?yàn)檫@里并不直接調(diào)用該方法。
Function<User, String> f1 = user->user.getName();
Function<User, String> f2 = User::getName;
Supplier<User> s1 = ()->new User();
Supplier<User> s2 = User::new;
Function<Integer, User[]> sa1 = count -> new User[count];
Function<Integer, User[]> sa2 = User[]::new;
方法引用主要分為如下幾種類型:
- 靜態(tài)方法引用:className::methodName
- 實(shí)例方法引用:instanceName::methodName
- 超類實(shí)體方法引用:supper::mehtodName
- 構(gòu)造函數(shù)方法引用:className::new
- 數(shù)組構(gòu)造方法引用:ClassName[]::new
2.4 類型推斷
類型推斷,是 Java7 就引入的目標(biāo)類型推斷的擴(kuò)展,在 Java8 中對(duì)其進(jìn)行了改善,程序員可以省略 Lambda 表達(dá)式中的所有參數(shù)類型,Javac 會(huì)根據(jù) Lambda 表達(dá)式式上下文信息自動(dòng)推斷出參數(shù)的正確類型。
大多數(shù)情況下 javac 能夠準(zhǔn)確的完成類型推斷,但由于 Lambda 表達(dá)式與函數(shù)名無(wú)關(guān),只與方法簽名相關(guān),因此會(huì)出現(xiàn)類型對(duì)推斷失效的情況,這時(shí)可以使用手工類型轉(zhuǎn)換幫助 javac 進(jìn)行正確的判斷。
// Supplier<String>, Callable<String> 具有相同的方法簽名
private void print(Supplier<String> stringSupplier){
System.out.println("Hello " + stringSupplier.get());
}
private void print(Callable<String> stringCallable){
try {
System.out.println("Hello " + stringCallable.call());
} catch (Exception e) {
e.printStackTrace();
}
}
{
// Error, 因?yàn)閮蓚€(gè)print同時(shí)滿足需求
print(()->"World");
// 使用類型轉(zhuǎn)換,為編譯器提供更多信息
print((Supplier<String>) ()->"World");
print((Callable<String>) ()-> "world");
}
3. 類庫(kù)的升級(jí)改造
Java8 另一個(gè)變化是引入了 默認(rèn)方法 和接口的 靜態(tài)方法 ,自此以后 Java 接口中方法也可以包含代碼體了。
3.1 默認(rèn)方法
默認(rèn)方法允許接口方法定義默認(rèn)實(shí)現(xiàn),而所有子類都將擁有該方法及實(shí)現(xiàn)。使其能夠在不改變子類實(shí)現(xiàn)的情況下(很多時(shí)候我們無(wú)法拿到子類的源碼),為所有子類添加新的功能,從而最大限度的保證二進(jìn)制接口的兼容性。
默認(rèn)方法的另一個(gè)優(yōu)勢(shì)是該方法是可選的,子類可以根據(jù)不同的需求 Override 默認(rèn)實(shí)現(xiàn),為其提供擴(kuò)展性保證。
其中 Collection 中的 forEach,stream 功能都是通過(guò)該技術(shù)統(tǒng)一添加到接口中的。
// Collection 中的forEache實(shí)現(xiàn)
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
// Collection中的stream實(shí)現(xiàn)
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
從上可見,默認(rèn)方法的寫法也是比較簡(jiǎn)單的,只需在方法聲明中添加 defalut 關(guān)鍵字,然后提供方法的默認(rèn)實(shí)現(xiàn)即可。
和類不同,接口中沒(méi)有成員變量,因此默認(rèn)方法只能通過(guò)調(diào)用子類的方法來(lái)修改子類本身,避免了對(duì)子類的實(shí)現(xiàn)做出各種假設(shè)。
3.1.1 默認(rèn)方法與子類
添加默認(rèn)方法特性后,方法的重寫規(guī)則也發(fā)生了變化,具體的場(chǎng)景如下:
a. 沒(méi)有重寫
沒(méi)有重寫是最簡(jiǎn)單的情況,子類調(diào)用該方法的時(shí)候,自然繼承了默認(rèn)方法。
interface Parent{
default void welcome(){
System.out.println("Parent");
}
}
// 調(diào)用Parent中的welcome, 輸入"Parent"
class ParentNotImpl implements Parent{
}
b. 子接口重寫
子接口對(duì)父接口中的默認(rèn)方法進(jìn)行了重新,其子類方法被調(diào)用時(shí),執(zhí)行子接口中的默認(rèn)方法
interface Parent{
default void welcome(){
System.out.println("Parent");
}
}
interface ChildInterface extends Parent{
@Override
default void welcome(){
System.out.println("ChildInterface");
}
}
// 執(zhí)行ChildInterface中的welcome, 輸入 "ChildInterface"
class ChildImpl implements ChildInterface{
}
c. 類重寫
一旦類中重寫了默認(rèn)方法,優(yōu)先選擇類中定義的方法,如果存在多級(jí)類繼承,遵循類繼承邏輯。
interface Parent{
default void welcome(){
System.out.println("Parent");
}
}
interface ChildInterface extends Parent{
@Override
default void welcome(){
System.out.println("ChildInterface");
}
}
//執(zhí)行子類中的welcome方法,輸出"ChildImpl"
class ChildImpl1 implements ChildInterface{
@Override
public void welcome(){
System.out.println("ChildImpl");
}
}
3.1.2 多重繼承
接口允許多重繼承,因此有可能會(huì)碰到兩個(gè)接口包含簽名相同的默認(rèn)方法的情況,此時(shí) javac 并不明確應(yīng)該繼承哪個(gè)接口中的方法,因此會(huì)導(dǎo)致編譯出錯(cuò),這時(shí)需要在類中實(shí)現(xiàn)該方法,如果想調(diào)用特定父接口中的默認(rèn)方法,可以使用 ParentInterface.super.method() 的方式來(lái)指明具體的接口。
interface Parent1 {
default void print(){
System.out.println("parent1");
}
}
interface Parent2{
default void print(){
System.out.println("parent2");
}
}
class Child implements Parent1, Parent2{
@Override
public void print() {
System.out.println("self");
Parent1.super.print();
Parent2.super.print();
}
}
現(xiàn)在的接口提供了某種形式上的多繼承功能,然而多重繼承存在很多詬病。很多人認(rèn)為多重繼承的問(wèn)題在于對(duì)象狀態(tài)的繼承,而不是代碼塊的繼承,默認(rèn)方法避免了狀態(tài)的繼承,也因此避免了 C++ 中多重繼承最大的缺點(diǎn)。
接口和抽象類之間還是有明顯的區(qū)別。接口允許多重繼承,卻沒(méi)有成員變量;抽象類可以繼承成員變量,卻不能多重繼承。
從某種角度出發(fā),Java 通過(guò)接口默認(rèn)方法實(shí)現(xiàn)了代碼多重繼承,通過(guò)類實(shí)現(xiàn)了狀態(tài)單一繼承。
3.1.3 三定律
如果對(duì)默認(rèn)方法的工作原理,特別是在多重繼承下的行為沒(méi)有把握,可以通過(guò)下面三條簡(jiǎn)單定律幫助大家。
- 類勝于方法。
如果在繼承鏈中有方法體或抽象的方法聲明,那么就可以忽略接口中定義的方法。 - 子類勝于父類。
如果一個(gè)接口繼承另一個(gè)接口,且兩個(gè)接口都定義了一個(gè)默認(rèn)方法,那么子接口中定義的方法勝出。 - 沒(méi)有規(guī)則三。
如果上面兩條規(guī)則不適用,子類要么實(shí)現(xiàn)該方法,要么將該方法聲明為抽象方法。
3.2 接口靜態(tài)方法
人們?cè)诰幊踢^(guò)程中積累了這樣一條經(jīng)驗(yàn),創(chuàng)建一個(gè)包含很多靜態(tài)方法的一個(gè)類。很多時(shí)候類是一個(gè)放置工具方法的好地方,比如 Java7 引入的 Objects 類,就包含很多工具方法,這些方法不是屬于具體的某個(gè)類。
如果一個(gè)方法有充分的語(yǔ)義原因和某個(gè)概念相關(guān),那么就應(yīng)該講該方法和相關(guān)的類或接口放在一起,而不是放到另一個(gè)工具類中,這非常有助于更好的組織代碼。
在接口中定義靜態(tài)方法,只需使用 static 關(guān)鍵字進(jìn)行描述即可,例如 Stream 接口中的 of 方法。
/**
* Returns a sequential {@code Stream} containing a single element.
*
* @param t the single element
* @param <T> the type of stream elements
* @return a singleton sequential stream
*/
public static<T> Stream<T> of(T t) {
return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
3.3 Stream
Stream 是 Java8 中最耀眼的亮點(diǎn),它使得程序員得以站在更高的抽象層次對(duì)集合進(jìn)行操作。
Stream 是用函數(shù)式編程方式在集合類上進(jìn)行復(fù)雜操作的工具。
3.3.1. 從外部迭代到內(nèi)部迭代
Java 程序員使用集合時(shí),一個(gè)通用模式就是在集合上進(jìn)行迭代,然后處理返回的每一個(gè)元素,盡管這種操作可行但存在幾個(gè)問(wèn)題:
- 大量的樣板代碼
- 模糊了程序本意
- 串行化執(zhí)行
常見集合遍歷如下:
// 常見寫法1,不推薦使用
public void printAll1(List<String> msg){
for (int i=0; i< msg.size(); i++){
String m = msg.get(i);
System.out.println(m);
}
}
// Java5之前,正確寫法,過(guò)于繁瑣
public void printAll2(List<String> msg){
Iterator<String> iterator = msg.iterator();
while (iterator.hasNext()){
String m = iterator.next();
System.out.println(m);
}
}
// Java5之后,加強(qiáng)for循環(huán),采用語(yǔ)法糖,簡(jiǎn)化for循環(huán),內(nèi)部轉(zhuǎn)化為Iterator方式
public void printAll3(List<String> msg){
for (String m : msg){
System.out.println(m);
}
}
整個(gè)迭代過(guò)程,通過(guò)顯示的調(diào)用 Iterator 對(duì)象的 hasNext 和 next 方法完成整個(gè)迭代,這成為外部迭代。
另一種方式成為內(nèi)部迭代,及將操作行為作為參數(shù)傳遞給 Stream,在 Stream 內(nèi)部完成迭代操作。
// Java8中,使用Stream進(jìn)行內(nèi)部迭代操作
public void printAll4(List<String> msg){
msg.stream().forEach(System.out::println);
}
內(nèi)部迭代:
3.3.2. 惰性求值 VS 及早求值
Stream 中存在兩類方法,不產(chǎn)生值的方法稱為惰性方法;從 Stream 中產(chǎn)生值的方法叫做及早求值方法。
判斷一個(gè)方法的類別很簡(jiǎn)單:如果返回值是 Stream,那么就是惰性方法;如果返回值是另一個(gè)值或?yàn)榭?,那么就是及早求值方法?/p>
惰性方法返回的 Stream 對(duì)象不是一個(gè)新的集合,而是創(chuàng)建新集合的配方,Stream 本身不會(huì)做任何迭代操作,只有調(diào)用及早求值方法時(shí),才會(huì)開始真正的迭代。
整個(gè)過(guò)程與 Builder 模式有共通之處,惰性方法負(fù)責(zé)對(duì) Stream 進(jìn)行裝配(設(shè)置 builder 的屬性),調(diào)用及早求值方法時(shí)(調(diào)用 builder 的 build 方法),按照之前的裝配信息進(jìn)行迭代操作。
常見 Stream 操作:
3.3.2.1 collect(toList())
及早求值方法:
collect(toList()) 方法由 Stream 里面的值生成一個(gè)列表,是一個(gè)及早求值操作。
collect 的功能不僅限于此,它是一個(gè)非常強(qiáng)大的結(jié)構(gòu)。
@Data
class User{
private String name;
}
public List<String> getNames(List<User> users){
List<String> names = new ArrayList<>();
for (User user : users){
names.add(user.getName());
}
return names;
}
public List<String> getNamesUseStream(List<User> users){
// 方法引用
//return users.stream().map(User::getName).collect(toList());
// lambda表達(dá)式
return users.stream().map(user -> user.getName()).collect(toList());
}
3.3.2.2. count、max、min
及早求值方法:
Stream 上最常用的操作之一就是求總數(shù)、最大值和最小值,count、max 和 min 足以解決問(wèn)題。
public Long getCount(List<User> users){
return users.stream().filter(user -> user != null).count();
}
// 求最小年齡
public Integer getMinAge(List<User> users){
return users.stream().map(user -> user.getAge()).min(Integer::compareTo).get();
}
// 求最大年齡
public Integer getMaxAge(List<User> users){
return users.stream().map(user -> user.getAge()).max(Integer::compareTo).get();
}
min 和 max 入?yún)⑹且粋€(gè) Comparator 對(duì)象,用于元素之間的比較,返回值是一個(gè) Optional<T>,它代表一個(gè)可能不存在的值,如果 Stream 為空,那么該值不存在,如果不為空,該值存在。通過(guò) get 方法可以獲取 Optional 中的值。
3.3.2.3 findAny、findFirst
及早求值方法:
兩個(gè)函數(shù)都以O(shè)ptional為返回值,用于表示是否找到。
public Optional<User> getAnyActiveUser(List<User> users){
return users.stream()
.filter(user -> user.isActive())
.findAny();
}
public Optional<User> getFirstActiveUser(List<User> users){
return users.stream()
.filter(user -> user.isActive())
.findFirst();
}
3.3.2.4 allMatch、anyMatch、noneMatch
及早求值方法:
均以 Predicate 作為輸入?yún)?shù),對(duì)集合中的元素進(jìn)行判斷,并返回最終的結(jié)果。
// 所有用戶是否都已激活
boolean allMatch = users.stream().allMatch(user -> user.isActive());
// 是否有激活用戶
boolean anyMatch = users.stream().anyMatch(user -> user.isActive());
// 是否所有用戶都沒(méi)有激活
boolean noneMatch = users.stream().noneMatch(user -> user.isActive());
3.3.2.6. forEach
及早求值:
以 Consumer 為參數(shù),對(duì) Stream 中復(fù)合條件的對(duì)象進(jìn)行操作。
public void printActiveName(List<User> users){
users.stream()
.filter(user -> user.isActive())
.map(user -> user.getName())
.forEach(name -> System.out.println(name));
}
3.3.2.7 reduce
及早求值方法:
reduce 操作可以實(shí)現(xiàn)從一組值中生成一個(gè)值,之前提到的 count、min、max 方法因?yàn)楸容^通用,單獨(dú)提取成方法,事實(shí)上,這些方法都是通過(guò) reduce 完成的。
下圖展示的是對(duì) stream 進(jìn)行求和的過(guò)程,以 0 為起點(diǎn),每一步都將 stream 中的元素累加到 accumulator 中,遍歷至最后一個(gè)元素,accumulator 就是所有元素值的和。
3.3.2.8. filter
惰性求值方法:
以 Predicate 作為參數(shù)(相當(dāng)于 if 語(yǔ)句),對(duì) Stream 中的元素進(jìn)行過(guò)濾,只有復(fù)合條件的元素才能進(jìn)入下面的處理流程。
處理流程如下:
public List<User> getActiveUser(List<User> users){
return users.stream()
.filter(user -> user.isActive())
.collect(toList());
}
3.3.2.9 map
及早求值方法:
以 Function 作為參數(shù),將 Stream 中的元素從一種類型轉(zhuǎn)換成另外一種類型。
處理過(guò)程如下:
public List<String> getNames(List<User> users){
return users.stream()
.map(user -> user.getName())
.collect(toList());
}
3.3.2.10 peek
Stream 提供的是內(nèi)迭代,有時(shí)候?yàn)榱斯δ苷{(diào)試,需要查看每個(gè)值,同時(shí)能夠繼續(xù)操作流,這時(shí)就會(huì)用到 peek 方法。
public void printActiveName(List<User> users){
users.stream()
.filter(user -> user.isActive())
.peek(user -> System.out.println(user.isActive()))
.map(user -> user.getName())
.forEach(name -> System.out.println(name));
}
3.3.2.11 其他
針對(duì)集合 Stream 還提供了許多功能強(qiáng)大的操作,暫不一一列舉,簡(jiǎn)單匯總一下。
- distinct:進(jìn)行去重操作
- sorted:進(jìn)行排序操作
- limit:限定結(jié)果輸出數(shù)量
- skip:跳過(guò) n 個(gè)結(jié)果,從 n+1 開始輸出
3.4 Optional
Java 程序中出現(xiàn)最多的異常就是 NullPointerException,沒(méi)有之一。Optional 的出現(xiàn)力求改變這一狀態(tài)。
Optional 對(duì)象相當(dāng)于值的容器,而該值可以通過(guò) get 方法獲取,同時(shí) Optional 提供了很多函數(shù)用于對(duì)值進(jìn)行操作,從而最大限度的避免 NullPointerException 的出現(xiàn)。
Optional 與 Stream 的用法基本類型,所提供的方法同樣分為惰性和及早求值兩類,惰性方法主要用于流程組裝,及早求值用于最終計(jì)算。
3.4.1 of
使用工廠方法 of,可以從一個(gè)值中創(chuàng)建一個(gè) Optional 對(duì)象,如果值為 null,會(huì)報(bào) NullPointerException。
Optional<String> dataOptional = Optional.of("a");
String data = dataOptional.get(); // data is "a"
Optional<String> dataOptional = Optional.of(null);
String data = dataOptional.get(); // throw NullPointerException
3.4.2 empty
工廠方法 empty,可以創(chuàng)建一個(gè)不包含任何值的 Optional 對(duì)象。
Optional<String> dataOptional = Optional.empty();
String data = dataOptional.get(); //throw NoSuchElementException
3.4.3 ofNullable
工廠方法 ofNullable,可將一個(gè)空值轉(zhuǎn)化成 Optional。
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
3.4.4 get、orElse、orElseGet、orElseThrow
直接求值方法,用于獲取 Optional 中值,避免空指針異常的出現(xiàn)。
Optional<String> dataOptional = Optional.of("a");
dataOptional.get(); // 獲取Optional中的值, 不存在會(huì)拋出NoSuchElementException
dataOptional.orElse("b"); //獲取Optional中的值,不存在,直接返回"B"
dataOptional.orElseGet(()-> String.valueOf(System.currentTimeMillis())); //獲取Optional中的值,不存在,對(duì)Supplier進(jìn)行計(jì)算,并返回計(jì)算結(jié)果
dataOptional.orElseThrow(()-> new XXXException()); //獲取Optional中的值,不存在,拋出自定義異常
3.4.5 isPresent、ifPresent
直接求值方法,isPresent 用于判斷 Optional 中是否有值,ifPresent 接收 Consumer 對(duì)象,當(dāng) Optional 有值的情況下執(zhí)行。
Optional<String> dataOptional = Optional.of("a");
String value = null;
if (dataOptional.isPresent()){
value = dataOptional.get();
}else {
value = "";
}
//等價(jià)于
String value2 = dataOptional.orElse("");
// 當(dāng)Optional中有值的時(shí)候執(zhí)行
dataOptional.ifPresent(v->System.out.println(v));
3.4.6 map
惰性求值方法。map 與 Stream 中的用法基本相同,用于對(duì) Optional 中的值進(jìn)行映射處理,從而避免了大量 if 語(yǔ)句嵌套,多個(gè) map 組合成鏈,只需對(duì)最終的結(jié)果進(jìn)行操作,中間過(guò)程中如果存在 null 值,之后的 map 不會(huì)執(zhí)行。
@Data
static class Order{
private Name owner;
}
@Data
static class User{
private Name name;
}
@Data
static class Name{
String firstName;
String midName;
String lastName;
}
private String getFirstName(Order order){
if (order == null){
return "";
}
if (order.getOwner() == null){
return "";
}
if (order.getOwner().getFirstName() == null){
return "";
}
return order.getOwner().getFirstName();
}
private String getFirstName(Optional<Order> orderOptional){
return orderOptional.map(order -> order.getOwner())
.map(user->user.getFirstName())
.orElse("");
}
3.4.7 filter
惰性求值,對(duì) Optional 中的值進(jìn)行過(guò)濾,如果 Optional 為 empty,直接返回 empty;如果 Optional 中存在值,則對(duì)值進(jìn)行驗(yàn)證,驗(yàn)證通過(guò)返回原 Optional,驗(yàn)證不通過(guò)返回 empty。
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
4. Lambda 下模式的進(jìn)化
設(shè)計(jì)模式是人們熟悉的一種設(shè)計(jì)思路,他是軟件架構(gòu)中解決通用問(wèn)題的模板,將解決特定問(wèn)題的最佳實(shí)踐固定下來(lái),但設(shè)計(jì)模式本身會(huì)比較復(fù)雜,包含多個(gè)接口、若干個(gè)實(shí)現(xiàn)類,應(yīng)用過(guò)程相對(duì)繁瑣,這也是影響其應(yīng)用的原因之一。
Lambda 表達(dá)式大大簡(jiǎn)化了 Java 中行為傳遞的問(wèn)題,對(duì)于很多行為式設(shè)計(jì)模式而言,減少了不少構(gòu)建成本。
4.1 命令模式
命令者是一個(gè)對(duì)象,其封裝了調(diào)用另一個(gè)方法的實(shí)現(xiàn)細(xì)節(jié),命令者模式使用該對(duì)象可以編寫根據(jù)運(yùn)行時(shí)條件,順序調(diào)用方法的一般性代碼。
大多數(shù)命令模式中的命令對(duì)象,其實(shí)是一種行為的封裝,甚至是對(duì)其他對(duì)象內(nèi)部行為的一種適配,這種情況下,Lambda 表達(dá)式并有了用武之地。
interface Command{
void act();
}
interface Editor{
void open();
void write(String data);
void save();
}
class CommandRunner{
private List<Command> commands = new ArrayList<>();
public void run(Command command){
command.act();
this.commands.add(command);
}
public void redo(){
this.commands.forEach(Command::act);
}
}
class OpenCommand implements Command{
private final Editor editor;
OpenCommand(Editor editor) {
this.editor = editor;
}
@Override
public void act() {
this.editor.open();
}
}
class WriteCommand implements Command{
private final Editor editor;
private final String data;
WriteCommand(Editor editor, String data) {
this.editor = editor;
this.data = data;
}
@Override
public void act() {
editor.write(this.data);
}
}
class SaveCommand implements Command{
private final Editor editor;
SaveCommand(Editor editor) {
this.editor = editor;
}
@Override
public void act() {
this.editor.save();
}
}
public void useCommand(){
CommandRunner commandRunner = new CommandRunner();
Editor editor = new EditorImpl();
String data1 = "data1";
String data2 = "data2";
commandRunner.run(new OpenCommand(editor));
commandRunner.run(new WriteCommand(editor, data1));
commandRunner.run(new WriteCommand(editor, data2));
commandRunner.run(new SaveCommand(editor));
}
public void useLambda(){
CommandRunner commandRunner = new CommandRunner();
Editor editor = new EditorImpl();
String data1 = "data1";
String data2 = "data2";
commandRunner.run(()->editor.open());
commandRunner.run(()->editor.write(data1));
commandRunner.run(()->editor.write(data2));
commandRunner.run(()->editor.save());
}
class EditorImpl implements Editor{
@Override
public void open() {
}
@Override
public void write(String data) {
}
@Override
public void save() {
}
}
從代碼中可見,Lambda 表達(dá)式的應(yīng)用,減少了創(chuàng)建子類的負(fù)擔(dān),增加了代碼的靈活性。
4.2 策略模式
策略模式能夠在運(yùn)行時(shí)改變軟件的算法行為,其核心的實(shí)現(xiàn)思路是,使用不同的算法來(lái)解決同一個(gè)問(wèn)題,然后將這些算法封裝在一個(gè)統(tǒng)一的接口背后。
可見策略模式也是一種行為行為傳遞的模式。
interface CompressionStrategy{
OutputStream compress(OutputStream outputStream) throws IOException;
}
class GzipBasedCompressionStrategy implements CompressionStrategy{
@Override
public OutputStream compress(OutputStream outputStream) throws IOException {
return new GZIPOutputStream(outputStream);
}
}
class ZipBasedCompressionStrategy implements CompressionStrategy{
@Override
public OutputStream compress(OutputStream outputStream) throws IOException {
return new ZipOutputStream(outputStream);
}
}
class Compressor{
private final CompressionStrategy compressionStrategy;
Compressor(CompressionStrategy compressionStrategy) {
this.compressionStrategy = compressionStrategy;
}
public void compress(Path inFile, File outFile) throws IOException {
try (OutputStream outputStream = new FileOutputStream(outFile)){
Files.copy(inFile, this.compressionStrategy.compress(outputStream));
}
}
}
{
Compressor gzipCompressor = new Compressor(new GzipBasedCompressionStrategy());
gzipCompressor.compress(in,out);
Compressor ziCompressor = new Compressor(new ZipBasedCompressionStrategy());
ziCompressor.compress(in,out);
}
{
Compressor gzipCompressor = new Compressor(GZIPOutputStream::new);
gzipCompressor.compress(in,out);
Compressor ziCompressor = new Compressor(ZipOutputStream::new);
ziCompressor.compress(in,out);
}
4.3 觀察者模式
觀察者模式中,被觀察者持有觀察者的一個(gè)列表,當(dāng)被觀察者的狀態(tài)發(fā)送變化時(shí),會(huì)通知觀察者。
對(duì)于一個(gè)觀察者來(lái)說(shuō),往往是對(duì)一個(gè)行為的封裝。
interface NameObserver{
void onNameChange(String oName, String nName);
}
@Data
class User {
private final List<NameObserver> nameObservers = new ArrayList<>();
@Setter(AccessLevel.PRIVATE)
private String name;
public void updateName(String nName){
String oName = getName();
setName(nName);
nameObservers.forEach(nameObserver -> nameObserver.onNameChange(oName, nName));
}
public void addObserver(NameObserver nameObserver){
this.nameObservers.add(nameObserver);
}
}
class LoggerNameObserver implements NameObserver{
@Override
public void onNameChange(String oName, String nName) {
System.out.println(String.format("old Name is %s, new Name is %s", oName, nName));
}
}
class NameChangeNoticeObserver implements NameObserver{
@Override
public void onNameChange(String oName, String nName) {
notic.send(String.format("old Name is %s, new Name is %s", oName, nName));
}
}
{
User user = new User();
user.addObserver(new LoggerNameObserver());
user.addObserver(new NameChangeNoticeObserver());
user.updateName("張三");
}
{
User user = new User();
user.addObserver((oName, nName) ->
System.out.println(String.format("old Name is %s, new Name is %s", oName, nName)));
user.addObserver((oName, nName) ->
notic.send(String.format("old Name is %s, new Name is %s", oName, nName)));
user.updateName("張三");
}
4.4 模板方法模式
模板方法將整體算法設(shè)計(jì)成一個(gè)抽象類,他有一系列的抽象方法,代表方法中可被定制的步驟,同時(shí)這個(gè)類中包含一些通用代碼,算法的每一個(gè)變種都由具體的類實(shí)現(xiàn),他們重新抽象方法,提供相應(yīng)的實(shí)現(xiàn)。
模板方法,實(shí)際是行為的一種整合,內(nèi)部大量用到行為的傳遞。
先看一個(gè)標(biāo)準(zhǔn)的模板方法:
interface UserChecker{
void check(User user);
}
abstract class AbstractUserChecker implements UserChecker{
@Override
public final void check(User user){
checkName(user);
checkAge(user);
}
abstract void checkName(User user);
abstract void checkAge(User user);
}
class SimpleUserChecker extends AbstractUserChecker {
@Override
void checkName(User user) {
Preconditions.checkArgument(StringUtils.isNotEmpty(user.getName()));
}
@Override
void checkAge(User user) {
Preconditions.checkArgument(user.getAge() != null);
Preconditions.checkArgument(user.getAge().intValue() > 0);
Preconditions.checkArgument(user.getAge().intValue() < 150);
}
}
{
UserChecker userChecker = new SimpleUserChecker();
userChecker.check(new User());
}
class LambdaBaseUserChecker implements UserChecker{
private final List<Consumer<User>> userCheckers = Lists.newArrayList();
public LambdaBaseUserChecker(List<Consumer<User>>userCheckers){
this.userCheckers.addAll(userCheckers);
}
@Override
public void check(User user){
this.userCheckers.forEach(userConsumer -> userConsumer.accept(user));
}
}
{
UserChecker userChecker = new LambdaBaseUserChecker(Arrays.asList(
user -> Preconditions.checkArgument(StringUtils.isNotEmpty(user.getName())),
user -> Preconditions.checkArgument(user.getAge() != null),
user -> Preconditions.checkArgument(user.getAge().intValue() > 0),
user -> Preconditions.checkArgument(user.getAge().intValue() < 150)
));
userChecker.check(new User());
}
@Data
class User{
private String name;
private Integer age;
}
在看一個(gè) Spring JdbcTemplate,如果使用 Lambda 進(jìn)行簡(jiǎn)化:
public JdbcTemplate jdbcTemplate;
public User getUserById(Integer id){
return jdbcTemplate.query("select id, name, age from tb_user where id = ?", new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setInt(1, id);
}
}, new ResultSetExtractor<User>() {
@Override
public User extractData(ResultSet resultSet) throws SQLException, DataAccessException {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setName(resultSet.getString("name"));
user.setAge(resultSet.getInt("age"));
return user;
}
});
}
public User getUserByIdLambda(Integer id){
return jdbcTemplate.query("select id, name, age from tb_user where id = ?",
preparedStatement -> preparedStatement.setInt(1, id),
resultSet -> {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setName(resultSet.getString("name"));
user.setAge(resultSet.getInt("age"));
return user;
});
}
@Data
class User {
private Integer id;
private String name;
private Integer age;
}
5. Lambda 下并發(fā)程序
并發(fā)與并行:
- 并發(fā)是兩個(gè)任務(wù)共享時(shí)間段,并行是兩個(gè)任務(wù)同一時(shí)間發(fā)生。
- 并行化是指為了縮短任務(wù)執(zhí)行的時(shí)間,將任務(wù)分解為幾個(gè)部分,然后并行執(zhí)行,這和順序執(zhí)行的工作量是一樣的,區(qū)別是多個(gè) CPU 一起來(lái)干活,花費(fèi)的時(shí)間自然減少了。
- 數(shù)據(jù)并行化。數(shù)據(jù)并行化是指將數(shù)據(jù)分為塊,為每塊數(shù)據(jù)分配獨(dú)立的處理單元。
5.1 并行化流操作
并行化流操作是 Stream 提供的一個(gè)特性,只需改變一個(gè)方法調(diào)用,就可以讓其擁有并行操作的能力。
如果已經(jīng)存在一個(gè) Stream 對(duì)象,調(diào)用他的 parallel 方法就能讓其并行執(zhí)行。
如果已經(jīng)存在一個(gè)集合,調(diào)用 parallelStream 方法就能獲取一個(gè)擁有并行執(zhí)行能力的 Stream。
并行流主要解決如何高效使用多核 CPU 的事情。
@Data
class Account{
private String name;
private boolean active;
private Integer amount;
}
public int getActiveAmount(List<Account> accounts){
return accounts.parallelStream()
.filter(account -> account.isActive())
.mapToInt(account -> account.getAmount())
.sum();
}
public int getActiveAmount2(List<Account> accounts){
return accounts.stream()
.parallel()
.filter(account -> account.isActive())
.mapToInt(Account::getAmount)
.sum();
}
并行流底層使用 fork/join 框架,fork 遞歸式的分解問(wèn)題,然后每個(gè)段并行執(zhí)行,最終有 join 合并結(jié)果,返回最后的值。
5.2 阻塞 IO VS 非阻塞 IO
BIO VS NIO
BIO 阻塞式 IO,是一種通用且容易理解的方式,與程序交互時(shí)通常都符合這種順序執(zhí)行的方式,但其主要的缺陷在于每個(gè) socket 會(huì)綁定一個(gè) Thread 進(jìn)行操作,當(dāng)長(zhǎng)鏈過(guò)多時(shí)會(huì)消耗大量的 Server 資源,從而導(dǎo)致其擴(kuò)展性性下降。
NIO 非阻塞 IO,一般指的是 IO 多路復(fù)用,可以使用一個(gè)線程同時(shí)對(duì)多個(gè) socket 的讀寫進(jìn)行監(jiān)控,從而使用少量線程服務(wù)于大量 Socket。
由于客戶端開發(fā)的簡(jiǎn)便性,大多數(shù)的驅(qū)動(dòng)都是基于 BIO 實(shí)現(xiàn),包括 MySQL、Redis、Mongo 等;在服務(wù)器端,由于其高性能的要求,基本上是 NIO 的天下,以最大限度的提升系統(tǒng)的可擴(kuò)展性。
由于客戶端存在大量的 BIO 操作,我們的客戶端線程會(huì)不停的被 BIO 阻塞,以等待操作返回值,因此線程的效率會(huì)大打折扣。
如上圖,線程在 IO 與 CPU 之間不停切換,走走停停,同時(shí)線程也沒(méi)有辦法釋放,一直等到任務(wù)完成。
5.3 Future
構(gòu)建并發(fā)操作的另一種方案便是 Future,F(xiàn)uture 是一種憑證,調(diào)用方法不是直接返回值,而是返回一個(gè) Future 對(duì)象,剛創(chuàng)建的 Future 為一個(gè)空對(duì)象,由后臺(tái)線程執(zhí)行耗時(shí)操作,并在結(jié)束時(shí)將結(jié)果寫回到 Future 中。
當(dāng)調(diào)用 Future 對(duì)象的 get 方法獲取值時(shí),會(huì)有兩個(gè)可能,如果后臺(tái)線程已經(jīng)運(yùn)行完成,則直接返回;如果后臺(tái)線程沒(méi)有運(yùn)行完成,則阻塞調(diào)用線程,知道后臺(tái)線程運(yùn)行完成或超時(shí)。
使用 Future 方式,可以以并行的方式運(yùn)行多個(gè)子任務(wù)。
當(dāng)主線程需要調(diào)用比較耗時(shí)的操作時(shí),可以將其放在輔助線程中執(zhí)行,并在需要數(shù)據(jù)的時(shí)候從 future 中獲取,如果輔助線程已經(jīng)運(yùn)行完成,則立即拿到返回的結(jié)果,如果輔助線程還沒(méi)有運(yùn)行完成,則主線程等待,并在完成時(shí)獲取結(jié)果。
一種常見的場(chǎng)景是在Controller中從多個(gè)Service中獲取結(jié)果,并將其封裝成一個(gè)View對(duì)象返回給前端用于顯示,假設(shè)需要從三個(gè)接口中獲取結(jié)果,每個(gè)接口的平均響應(yīng)時(shí)間是20ms,那按照串行模式,總耗時(shí)為sum(i1, i2, i3) = 60ms;如果按照Future并發(fā)模式將加載任務(wù)交由輔助線程處理,總耗時(shí)為max(i1, i2, i3 ) = 20ms, 大大減少了系統(tǒng)的響應(yīng)時(shí)間。
private ExecutorService executorService = Executors.newFixedThreadPool(20);
private User loadUserByUid(Long uid){
sleep(20);
return new User();
}
private Address loadAddressByUid(Long uid){
sleep(20);
return new Address();
}
private Account loadAccountByUid(Long uid){
sleep(20);
return new Account();
}
/**
* 總耗時(shí) sum(LoadUser, LoadAddress, LoadAccount) = 60ms
* @param uid
* @return
*/
public View getViewByUid1(Long uid){
User user = loadUserByUid(uid);
Address address = loadAddressByUid(uid);
Account account = loadAccountByUid(uid);
View view = new View();
view.setUser(user);
view.setAddress(address);
view.setAccount(account);
return view;
}
/**
* 總耗時(shí) max(LoadUser, LoadAddress, LoadAccount) = 20ms
* @param uid
* @return
* @throws ExecutionException
* @throws InterruptedException
*/
public View getViewByUid(Long uid) throws ExecutionException, InterruptedException {
Future<User> userFuture = executorService.submit(()->loadUserByUid(uid));
Future<Address> addressFuture = executorService.submit(()->loadAddressByUid(uid));
Future<Account> accountFuture = executorService.submit(()->loadAccountByUid(uid));
View view = new View();
view.setUser(userFuture.get());
view.setAddress(addressFuture.get());
view.setAccount(accountFuture.get());
return view;
}
private void sleep(long time){
try {
TimeUnit.MILLISECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Data
class View{
private User user;
private Address address;
private Account account;
}
class User{
}
class Address{
}
class Account{
}
Future 方式存在一個(gè)問(wèn)題,及在調(diào)用 get 方法時(shí)會(huì)阻塞主線程,這是資源的極大浪費(fèi),我們真正需要的是一種不必調(diào)用 get 方法阻塞當(dāng)前線程,就可以操作 future 對(duì)象返回的結(jié)果。
上例中只是子任務(wù)能夠拆分并能并行執(zhí)行的一種典型案例,在實(shí)際開發(fā)過(guò)程中,我們會(huì)遇到更多、更復(fù)雜的場(chǎng)景,比如:
- 將兩個(gè) Future 結(jié)果合并成一個(gè),同時(shí)第二個(gè)又依賴于第一個(gè)的結(jié)果
- 等待 Future 集合中所有記錄的完成
- 等待 Future 集合中的最快的任務(wù)完成
- 定義任務(wù)完成后的操作
對(duì)此,我們引入了 CompletableFuture 對(duì)象。
5.4 CompletableFuture
- CompletableFuture 結(jié)合了 Future 和回調(diào)兩種策略,以更好的處理事件驅(qū)動(dòng)任務(wù)。
- CompletableFuture 與Stream 的設(shè)計(jì)思路一致,通過(guò)注冊(cè) Lambda 表達(dá)式,把高階函數(shù)鏈接起來(lái),從而定制更復(fù)雜的處理流程。
CompletableFuture 提供了一組函數(shù)用于定義流程,其中包括:
5.4.1 創(chuàng)建函數(shù)
CompletableFuture 提供了一組靜態(tài)方法用于創(chuàng)建 CompletableFuture 實(shí)例:
public static <U> CompletableFuture<U> completedFuture(U value)// :使用已經(jīng)創(chuàng)建好的值,創(chuàng)建 CompletableFuture 對(duì)象。
public static CompletableFuture<Void> runAsync(Runnable runnable)// 基于 Runnable 創(chuàng)建 CompletableFuture 對(duì)象,返回值為 Void,及沒(méi)有返回值
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)// 基于 Runnable 和自定義線程池創(chuàng)建 CompletableFuture 對(duì)象,返回值為 Void,及沒(méi)有返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)// 基于 Supplier 創(chuàng)建 CompletableFuture 對(duì)象,返回值為 U
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) // 基于 Supplier 和自定義線程池創(chuàng)建 CompletableFuture 對(duì)象,返回值為 U
以 Async 結(jié)尾并且沒(méi)有指定 Executor 的方法會(huì)使用 ForkJoinPool.commonPool() 作為它的線程池執(zhí)行異步代碼。
方法的參數(shù)類型都是函數(shù)式接口,所以可以使用 Lambda 表達(dá)式實(shí)現(xiàn)異步任務(wù)。
5.4.2 計(jì)算結(jié)果完成后
當(dāng) CompletableFuture 計(jì)算完成或者計(jì)算過(guò)程中拋出異常時(shí)進(jìn)行回調(diào)。
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
Action 的類型是 BiConsumer<? super T,? super Throwable> 它可以處理正常的計(jì)算結(jié)果,或者異常情況。
方法不以 Async 結(jié)尾,意味著 Action 使用相同的線程執(zhí)行,而 Async 可能會(huì)使用其他線程執(zhí)行(如果是使用相同的線程池,也可能會(huì)被同一個(gè)線程選中執(zhí)行)。
exceptionally 針對(duì)異常情況進(jìn)行處理,當(dāng)原始的 CompletableFuture 拋出異常的時(shí)候,就會(huì)觸發(fā)這個(gè) CompletableFuture 的計(jì)算。
下面一組方法雖然也返回 CompletableFuture 對(duì)象,但是對(duì)象的值和原來(lái)的 CompletableFuture 計(jì)算的值不同。當(dāng)原先的 CompletableFuture 的值計(jì)算完成或者拋出異常的時(shí)候,會(huì)觸發(fā)這個(gè) CompletableFuture 對(duì)象的計(jì)算,結(jié)果由 BiFunction 參數(shù)計(jì)算而得。因此這組方法兼有 whenComplete 和轉(zhuǎn)換的兩個(gè)功能。
public <?U> CompletableFuture<?U> handle(BiFunction<? super T,Throwable,? extends U> fn)
public <?U> CompletableFuture<?U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <?U> CompletableFuture<?U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)
5.4.3 轉(zhuǎn)化函數(shù)
轉(zhuǎn)化函數(shù)類似于 Stream 中的惰性求助函數(shù),主要對(duì) CompletableFuture 的中間結(jié)果進(jìn)行流程定制。
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
通過(guò)函數(shù)完成對(duì) CompletableFuture 中的值得轉(zhuǎn)化,Async 在線的線程池中處理,Executor 可以自定義線程池。
5.4.4 純消費(fèi)函數(shù)
上面的方法當(dāng)計(jì)算完成的時(shí)候,會(huì)生成新的計(jì)算結(jié)果 (thenApply, handle),或者返回同樣的計(jì)算結(jié)果 whenComplete,CompletableFuture 還提供了一種處理結(jié)果的方法,只對(duì)結(jié)果執(zhí)行 Action,而不返回新的計(jì)算值,因此計(jì)算值為 Void。
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
其他的參數(shù)類型與之前的含義一致,不同的是函數(shù)接口 Consumer,這個(gè)接口只有輸入,沒(méi)有返回值。
thenAcceptBoth 以及相關(guān)方法提供了類似的功能,當(dāng)兩個(gè) CompletionStage 都正常完成計(jì)算的時(shí)候,就會(huì)執(zhí)行提供的 action,它用來(lái)組合另外一個(gè)異步的結(jié)果。
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<?
extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends
U> other, BiConsumer<? super T,? super U> action)
public <U>
CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends
U> other, BiConsumer<? super T,? super U> action, Executor executor)
5.4.5. 組合函數(shù)
組合函數(shù)主要應(yīng)用于后續(xù)計(jì)算需要 CompletableFuture 計(jì)算結(jié)果的場(chǎng)景。
public <U> CompletableFuture<U> thenCompose(Function<? super T,? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn, Executor executor)
這一組方法接受一個(gè) Function 作為參數(shù),這個(gè) Function 的輸入是當(dāng)前的 CompletableFuture 的計(jì)算值,返回結(jié)果將是一個(gè)新的 CompletableFuture,這個(gè)新的 CompletableFuture 會(huì)組合原來(lái)的 CompletableFuture 和函數(shù)返回的 CompletableFuture。因此它的功能類似:
A +–> B +—> C
下面的一組方法 thenCombine 用來(lái)復(fù)合另外一個(gè) CompletionStage 的結(jié)果。兩個(gè) CompletionStage 是并行執(zhí)行的,它們之間并沒(méi)有先后依賴順序,other 并不會(huì)等待先前的 CompletableFuture 執(zhí)行完畢后再執(zhí)行,當(dāng)兩個(gè) CompletionStage 全部執(zhí)行完成后,統(tǒng)一調(diào)用 BiFunction 函數(shù),計(jì)算最終的結(jié)果。
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<?
extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<?
extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<?
extends U> other, BiFunction<? super T,? super U,? extends V> fn,
Executor executor)
5.4.6. Either
Either 系列方法不會(huì)等兩個(gè) CompletableFuture 都計(jì)算完成后執(zhí)行計(jì)算,而是當(dāng)任意一個(gè) CompletableFuture 計(jì)算完成的時(shí)候就會(huì)執(zhí)行。
public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)
public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)
5.4.7 輔助方法
輔助方法主要指 allOf 和 anyOf,這兩個(gè)靜態(tài)方法用于組合多個(gè) CompletableFuture。
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)// allOf方法是當(dāng)所有的CompletableFuture都執(zhí)行完后執(zhí)行計(jì)算。
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)// anyOf方法是當(dāng)任意一個(gè)CompletableFuture執(zhí)行完后就會(huì)執(zhí)行計(jì)算。