Stream
集合擴展類,通過Collection.stream()
和Collection.parallelStream()
來創建一個Stream。
Stream常用操作
下邊操作例子數據源
List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");
- Filter
過濾通過一個predicate接口來過濾并只保留符合條件的元素,該操作屬于中間操作,所以我們可以在過濾后的結果來應用其他Stream操作(比如forEach)。forEach需要一個函數來對過濾后的元素依次執行。forEach是一個最終操作,所以我們不能在forEach之后來執行其他Stream操作。
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
- Sort
排序是一個中間操作,返回的是排序好后的Stream。如果你不指定一個自定義的Comparator則會使用默認排序。需要注意的是,排序只創建了一個排列好后的Stream,而不會影響原有的數據源,排序之后原數據是不會被修改的。
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
- Map 映射
中間操作map會將元素根據指定的Function接口來依次將元素轉成另外的對象,下面的示例展示了將字符串轉換為大寫字符串。你也可以通過map來講對象轉換成其他類型,map返回的Stream類型是根據你map傳遞進去的函數的返回值決定的。
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
- Match 匹配
Stream提供了多種匹配操作,允許檢測指定的Predicate是否匹配整個Stream。所有的匹配操作都是最終操作,并返回一個boolean類型的值。
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
- Count 計數
計數是一個最終操作,返回Stream中元素的個數,返回值類型是long。
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
- Reduce 規約
這是一個最終操作,允許通過指定的函數來講stream中的多個元素規約為一個元素,規越后的結果是通過Optional接口表示的。
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
- 并行Streams
前面提到過Stream有串行和并行兩種,串行Stream上的操作是在一個線程中依次完成,而并行Stream則是在多個線程上同時執行。
首先我們創建一個沒有重復元素的大表,然后我們計算一下排序這個Stream要耗時多久
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
串行排序:
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
// 串行耗時: 899 ms
并行排序:
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
// 并行排序耗時: 472 ms
上面兩個代碼幾乎是一樣的,但是并行版的快了50%之多,唯一需要做的改動就是將stream()改為parallelStream()。
Lumbda 、Executors處理線程并發
- 創建新的線程
new Thread(() -> System.out.println("Single Thread Run.............")).start();
- ExecutorService管理無返回值的線程(ExecutorService+runnable)
Executos支持運行異步任務,通常管理一個線程池,這樣一來我們就不需要手動去創建新的線程。
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("thread managed by executorservice……");
});
try {
System.out.println("嘗試關閉ExecutorService");
executorService.shutdown();
//指定一段時間溫和關閉
executorService.awaitTermination(5, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
System.out.println("任務中斷……");
}
finally {
if (!executorService.isTerminated()) {
System.out.println("結束未完成的任務……");
}
executorService.shutdownNow();
System.out.println("ExecutorService被停止……");
}
注:Java進程從沒有停止!Executors必須顯式的停止-否則它們將持續監聽新的任務。如果執行executorService.shutdown();時任務未終止,會報java.lang.InterruptedException: sleep interrupted異常。
- ExecutorService管理有返回值的線程(ExecutorService+callable+future)
Callable<String>callable = ()-> {
TimeUnit.SECONDS.sleep(4);
return "managed by executor and have result to return";
};
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(callable);
try {
String result = future.get();
System.out.print(result);
} catch (Exception e) {
e.printStackTrace();
}
注:future.get()是一個阻塞的方法,上述代碼中大約4s之后值才輸出出來
- Executors批量處理多個callable并返回所有callable的運行結果(Executor+callable+future+invokeAll)
private static void testInvokeAll(){
ExecutorService executorService = Executors.newWorkStealingPool();
List<Callable<String>> callables = Arrays.asList(getCallable("download apk...........", 4),getCallable("download files...........", 10),getCallable("download pictures...........", 6));
try {
executorService.invokeAll(callables)
.stream()
.map(future ->{
try{
return future.get();
}catch (Exception e) {
e.printStackTrace();
return "";
}
})
.forEach(System.out::println);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static Callable<String> getCallable(String s,long time){
Callable<String> callable = ()-> {
TimeUnit.SECONDS.sleep(time);
return s;
};
return callable;
}
注:三個任務執行的時間分別為4s、10s、6s,invokeAll會在所有的任務都執行完也就是10s之后才輸出結果
- Executors批量處理多個callable并返回運行最快的callable的運行結果(Executor+invokeAny)
long startTime = System.currentTimeMillis();
ExecutorService executorService = Executors.newWorkStealingPool();
List<Callable<String>> callables = Arrays.asList(getCallable("download apk...........", 4),getCallable("download files...........", 10),getCallable("download pictures...........", 6));
try {
String result = executorService.invokeAny(callables);
System.out.println("執行..."+result+"...花了........."+(System.currentTimeMillis() - startTime)/1000 +"s..............");
} catch (Exception e) {
e.printStackTrace();
}
注:invokeAll返回集合中所有callable的結果,invokeAny只返回一個值,即運行最快的那個callable的值
- Executors延遲一段時間執行任務(executorService.schedule(task,time,timeUnit))
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.schedule(() -> System.out.println("test delay runnable.............."), 3, TimeUnit.SECONDS);
- Executors以固定時間執行任務(executorService.scheduleAtFixedRate())
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleAtFixedRate(() -> System.out.println("test fixed delay runnable.............."), 3,5, TimeUnit.SECONDS);
3s后第一次輸出結果,然后每5s執行一次任務
注:scheduleAtFixedRate()并不考慮任務的實際用時。所以,如果你指定了一個period為1分鐘而任務需要執行2分鐘,那么線程池為了性能會更快的執行。
- Executors兩次任務之間以固定的間隔執行(executorService.scheduleWithFixedDelay())
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleWithFixedDelay(() ->
{
System.out.println("test fixed delay runnable..............");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 3,5, TimeUnit.SECONDS);
注:該方法是在3s后第一次執行任務輸出結果,然后在任務執行完后的時間間隔是5,即以后每隔7s輸出一次結果(執行任務的時間+任務間隔)
CompletableFuture
CompletableFuture有兩個主要的方面優于ol中的Future – 異步回調/轉換,這能使得從任何時刻的任何線程都可以設置CompletableFuture的值。
- 創造和獲取CompletableFuture
手動地創建CompletableFuture是我們唯一的選擇嗎?不一定。就像一般的Futures,我們可以關聯存在的任務,同時CompletableFuture使用工廠方法:
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
static CompletableFuture<Void> runAsync(Runnable runnable);
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
runAsync()易于理解,注意它需要Runnable,因此它返回CompletableFuture<Void>
作為Runnable不返回任何值。如果你需要處理異步操作并返回結果,使用Supplier<U>
:
final CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
//...long running...
return "42";
}, executor);
//或
final CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> longRunningTask(params), executor);
- 單個CompletableFuture的錯誤處理
CompletableFuture<String> safe =
future.exceptionally(ex -> "We have a problem: " + ex.getMessage());
exceptionally()接受一個函數時,將調用原始future來拋出一個異常。我們會有機會將此異常轉換為和Future類型的兼容的一些值來進行恢復。safe進一步的轉換將不再產生一個異常而是從提供功能的函數返回一個String值。
一個更加靈活的方法是handle()接受一個函數,它接收正確的結果或異常:
CompletableFuture<Integer> safe = future.handle((ok, ex) -> {
if (ok != null) {
return Integer.parseInt(ok);
} else {
log.warn("Problem", ex);
return -1;
}
});
- 轉換和作用于CompletableFuture(thenApply)
<U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn);
<U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn);
<U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor);
例子:
CompletableFuture<String> f1 = //...
CompletableFuture<Integer> f2 = f1.thenApply(Integer::parseInt);
CompletableFuture<Double> f3 = f2.thenApply(r -> r * r * Math.PI);
- 運行完成的代碼(thenAccept/thenRun)
CompletableFuture<Void> thenAccept(Consumer<? super T> block);
CompletableFuture<Void> thenRun(Runnable action);
在future的管道里有兩種典型的“最終”階段方法。他們在你使用future的值的時候做好準備,當 thenAccept()提供最終的值時,thenRun執行 Runnable,這甚至沒有方法去計算值。例如:
future.thenAcceptAsync(dbl -> log.debug("Result: {}", dbl), executor);
log.debug("Continuing");
…Async變量也可用兩種方法,隱式和顯式執行器,我不會過多強調這個方法。
thenAccept()/thenRun()方法并沒有發生阻塞(即使沒有明確的executor)。它們像一個事件偵聽器/處理程序,你連接到一個future時,這將執行一段時間?!盋ontinuing”消息將立即出現,盡管future甚至沒有完成。
- 結合(鏈接)這兩個futures(thenCompose())
時你想運行一些future的值(當它準備好了),但這個函數也返回了future。CompletableFuture足夠靈活地明白我們的函數結果現在應該作為頂級的future,對比CompletableFuture<CompletableFuture>
。方法 thenCompose()相當于Scala的flatMap:
<U> CompletableFuture<U> thenCompose(Function<? super T,CompletableFuture<U>> fn);
…Async變化也是可用的,在下面的事例中,仔細觀察thenApply()(map)和thenCompose()(flatMap)的類型和差異,當應用calculateRelevance()方法返回CompletableFuture:
CompletableFuture<Document> docFuture = //...
CompletableFuture<CompletableFuture<Double>> f =
docFuture.thenApply(this::calculateRelevance);
CompletableFuture<Double> relevanceFuture =
docFuture.thenCompose(this::calculateRelevance);
//...
private CompletableFuture<Double> calculateRelevance(Document doc) //...
- 兩個futures的轉換值(thenCombine())
當thenCompose()用于鏈接一個future時依賴另一個thenCombine,當他們都完成之后就結合兩個獨立的futures:
<U,V> CompletableFuture<V> thenCombine(CompletableFuture<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
…Async變量也是可用的,假設你有兩個CompletableFuture,一個加載Customer另一個加載最近的Shop。他們彼此完全獨立,但是當他們完成時,您想要使用它們的值來計算Route。這是一個可剝奪的例子:
CompletableFuture<Customer> customerFuture = loadCustomerDetails(123);
CompletableFuture<Shop> shopFuture = closestShop();
CompletableFuture<Route> routeFuture =
customerFuture.thenCombine(shopFuture, (cust, shop) -> findRoute(cust, shop));
//...
private Route findRoute(Customer customer, Shop shop) //...
你也知道,我們有customerFuture 和 shopFuture。那么routeFuture包裝它們然后“等待”它們完成。當他們準備好了,它會運行我們提供的函數來結合所有的結果(findRoute())。當兩個基本的futures完成并且 findRoute()也完成時,這樣routeFuture將會完成。
- 等待所有的 CompletableFutures 完成
如果不是產生新的CompletableFuture連接這兩個結果,我們只是希望當完成時得到通知,我們可以使用thenAcceptBoth()/runAfterBoth()系列的方法,(…Async 變量也是可用的)。它們的工作方式與thenAccept() 和 thenRun()類似,但是是等待兩個futures而不是一個:
<U> CompletableFuture<Void> thenAcceptBoth(CompletableFuture<? extends U> other, BiConsumer<? super T,? super U> block)
CompletableFuture<Void> runAfterBoth(CompletableFuture<?> other, Runnable action)
- 等待第一個 CompletableFuture 來完成任務
另一個有趣的事是CompletableFutureAPI可以等待第一個(與所有相反)完成的future。當你有兩個相同類型任務的結果時就顯得非常方便,你只要關心響應時間就行了,沒有哪個任務是優先的。API方法(…Async變量也是可用的):
CompletableFuture<Void> acceptEither(CompletableFuture<? extends T> other, Consumer<? super T> block)
CompletableFuture<Void> runAfterEither(CompletableFuture<?> other, Runnable action)
作為一個例子,你有兩個系統可以集成。一個具有較小的平均響應時間但是擁有高的標準差,另一個一般情況下較慢,但是更加容易預測。為了兩全其美(性能和可預測性)你可以在同一時間調用兩個系統并等著誰先完成。通常這會是第一個系統,但是在進度變得緩慢時,第二個系統就可以在可接受的時間內完成:
CompletableFuture<String> fast = fetchFast();
CompletableFuture<String> predictable = fetchPredictably();
fast.acceptEither(predictable, s -> {
System.out.println("Result: " + s);
});
s代表了從fetchFast()或是fetchPredictably()得到的String。我們不必知道也無需關心。
- 完整地轉換第一個系統
applyToEither()算是 acceptEither()的前輩了。當兩個futures快要完成時,后者只是簡單地調用一些代碼片段,applyToEither()將會返回一個新的future。當這兩個最初的futures完成時,新的future也會完成。API有點類似于(…Async 變量也是可用的):
<U> CompletableFuture<U> applyToEither(CompletableFuture<? extends T> other, Function<? super T,U> fn)
這個額外的fn功能在第一個future被調用時能完成。我不確定這個專業化方法的目的是什么,畢竟一個人可以簡單地使用:fast.applyToEither(predictable).thenApply(fn)。因為我們堅持用這個API,但我們的確不需要額外功能的應用程序,我會簡單地使用Function.identity()占位符:
CompletableFuture<String> fast = fetchFast();
CompletableFuture<String> predictable = fetchPredictably();
CompletableFuture<String> firstDone =
fast.applyToEither(predictable, Function.<String>identity());
第一個完成的future可以通過運行。請注意,從客戶的角度來看,兩個futures實際上是在firstDone的后面而隱藏的??蛻舳酥皇堑却鴉uture來完成并且通過applyToEither()使得當最先的兩個任務完成時通知客戶端。
- 多種結合的CompletableFuture
我們現在知道如何等待兩個future來完成(使用thenCombine())并第一個完成(applyToEither())。但它可以擴展到任意數量的futures嗎?的確,使用static輔助方法:
static CompletableFuture<Void< allOf(CompletableFuture<?<... cfs)
static CompletableFuture<Object< anyOf(CompletableFuture<?<... cfs)