序:本篇內容較多,排版有些亂,希望大家理解
目錄
流 Stream/parallelStream【構建器模式】
23.並行——使用流還是 CompletableFutures
尾-調優化(tail-call optimization)Java8 目前不支持
關鍵詞
行爲參數化
把方法(你的代碼)作爲參數傳遞給另一個方法的能力
方法引用
符號 ‘::’
以(數學)函數作爲參數傳遞給方法,參數可以理解爲值
函數:無可變共享狀態,可以有效、安全地並行執行
類別:
1.指向靜態方法的方法引用
2.指向任 意類型實例方法的方法引用
3.指向現有對象的實例方法的方法引用
// Lambda表達式的簽名與函數描述符兼容
List<String> str = Arrays.asList("a","b","A","B");
str.sort((s1, s2) -> s1.compareToIgnoreCase(s2));
str.sort(String::compareToIgnoreCase);
Function<String, Integer> stringToInteger = (String s) -> Integer.parseInt(s);
Function<String, Integer> stringToInteger = Integer::parseInt;
BiPredicate<List<String>, String> contains = (list, element) -> list.contains(element);
BiPredicate<List<String>, String> contains = List::contains;
// 構造函數引用
// 無參
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
// 等價於
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
// 有參
Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apply(110);
// 等價於
Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Apple a2 = c2.apply(110);
流 Stream/parallelStream【構建器模式】
比喻:車間流水線式工作
特點:透明地並行處理,高級迭代器
定義:從支持數據處理操作的源生成的元素序列
操作:中間操作{連接起來的流操作} 終端操作{關閉流的操作}
應用場景:IO密集,CPU密集
對比:
java.util.stream.Stream<T> 串行執行
java.util.Collection<E>.parallelStream 多核架構並行執行 【ForkJoinPool】【慎用】
函數:
1.reduce 根據一定的規則將Stream中的元素進行計算後返回一個唯一的值。
考慮使用parallelStream
1.是否需要並行?
2.任務之間是否是獨立的?是否會引起任何競態條件?
3.結果是否取決於任務的調用順序?
Stream API帶來的代碼特性
1.聲明性——更簡潔,更易讀
2.可複合——更靈活
3.可並行——性能更好
使用流
1.一個數據源(如集合)來執行一個查詢
2.一箇中間操作鏈,形成一條流的流水線
3.一個終端操作,執行流水線,並能生成結果
// 舉例
long count = list.stream()
.filter(d -> d.getCalories() > 300)
.distinct()
.limit(3)
.count();
List<String> names = list.stream()
.filter(d -> d.getCalories() > 300)
.map(Dish::getName)
.limit(3)
.collect(toList());
// 歸約:歸類、摺疊(folk), 反覆迭代
// 有初始值 初始值,BinaryOperator<T>
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
// 無初始值 BinaryOperator<T>
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
// 數值流
// IntStream、DoubleStream、LongStream,分別將流中的元素特化爲 int、long、double, 避免了暗含的裝箱成本
// 映射到數值流
// mapToInt、mapToDouble、mapToLong
IntStream intStream = intList.stream().mapToInt(Dish::getValue);
// 轉換回對象流
Stream<Integer> stream = intStream.boxed();
// 默認值
// Optional 原始類型特化版本: OptionalInt、OptionalDouble、OptionalLong
// 數值範圍
// Java 8引入了兩個可以用於 IntStream 和 LongStream 的靜態方法
IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
// 由值創建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
Stream<String> emptyStream = Stream.empty();
// 由數組創建流
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
// 由文件生成流
long uniqueWords = 0;
try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
}
catch(IOException e){
}
// 由函數生成流:創建無限流
// Stream API提供了兩個靜態方法來從函數生成流: Stream.iterate 和 Stream.generate
// 迭代
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
// 生成
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
分支步驟
完成一個任務,將其分解成若干個任務,並行處理,然後合併,最終將任務完成
默認方法
符號 ‘default’
擴充現有接口,不影響實現類的代碼;
在接口中提供默認的實現方法
(結構)模式匹配【不完全支持】
f(0) = 1
f(n) = n*f(n-1) otherwise
行爲參數化【工廠模式】
應對頻繁變更的需求,靈活,簡潔
讓方法接受多種行爲(或戰略)作爲參數,並在內部使用,來完成不同的行爲
是類、匿名類、Lambda的集合體
Lambda表達式
可以理解成是一種匿名類,或是一種接口的實現類
代碼更加簡潔
一種編程風格
① 用處:
1.函數式接口
只定義一個抽象方法的接口,用來傳遞行爲
接口設計 @FunctionalInterface
1.1.Predicate【謂語】【測試者】
java.util.function.Predicate<T> 接口定義了一個名叫 test 的抽象方法,它接受泛型 T 對象,並返回一個 boolean 。
@FunctionalInterface
public interface Predicate<T>{
boolean test(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> results = new ArrayList<>();
for(T s: list){
if(p.test(s)){
results.add(s);
}
}
return results;
}
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
1.2.Consumer【消費者】
java.util.function.Consumer<T> 定義了一個名叫 accept 的抽象方法,它接受泛型 T 的對象,沒有返回( void )。你如果需要訪問類型 T 的對象,並對其執行某些操作,就可以使用這個接口。T 只能是引用類型。
@FunctionalInterface
public interface Consumer<T>{
void accept(T t);
}
public static <T> void forEach(List<T> list, Consumer<T> c){
for(T i: list){
c.accept(i);
}
}
forEach(Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i));
1.3.Function【功能】【類型轉換】
java.util.function.Function<T, R> 接口定義了一個叫作 apply 的方法,它接受一個泛型 T 的對象,並返回一個泛型 R 的對象。
@FunctionalInterface
public interface Function<T, R>{
R apply(T t);
}
public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
List<R> result = new ArrayList<>();
for(T s: list){
result.add(f.apply(s));
}
return result;
}
// [7, 2, 6]
List<Integer> l = map(Arrays.asList("lambdas","in","action"), (String s) -> s.length());
1.4.Supplier【生產者】
java.util.function.Supplier<T>接口接口定義了一個叫作 get 的方法,它接受一個泛型 T 的對象,並返回一個泛型 T 的對象。
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Supplier<String> supplier = String::new;
② 類型檢查過程
1.確定目標函數類型
2.執行目標函數
③ 限制條件
1.局部變量必須顯式聲明爲 final,保存在棧上。(產生原因是多線程,本質原因是存儲的位置不同)
收集器【靜態工廠 Collectors】【工廠方法】
功能:
1.將流元素歸約和彙總爲一個值
2.元素分組
3.元素分區
/** 一下是Collectors.reducing 工廠方法提供的廣義歸約收集器的特殊情況 **/
// 歸約collect,計算總數
long howManyDishes = menu.stream().collect(Collectors.counting());
long howManyDishes = menu.stream().count();
// 查找流中的最大值和最小值
Comparator<Dish> dishValuesComparator = Comparator.comparingInt(Dish::getValues);
Optional<Dish> mostValuesDishMax =
menu.stream().collect(Collectors.maxBy(dishCaloriesComparator));
Optional<Dish> mostValueDishMin =
menu.stream().collect(Collectors.minBy(dishCaloriesComparator));
// 彙總
int totalValues =
menu.stream().collect(Collectors.summingInt(Dish::getValues));
double totalValues =
menu.stream().collect(Collectors.summingDouble(Dish::getValues));
long totalValues =
menu.stream().collect(Collectors.summingLong(Dish::getValues));
// 計算數值的平均數
int avgValues =
menu.stream().collect(Collectors.averagingInt(Dish::getValues));
double avgValues =
menu.stream().collect(Collectors.averagingDouble(Dish::getValues));
long avgValues =
menu.stream().collect(Collectors.averagingLong(Dish::getValues));
// 利用summarizingInt工廠方法返回的收集器,一次性獲取總和、平均值、最大值和最小值
IntSummaryStatistics statistics =
menu.stream().collect(Collectors.summarizingInt(Dish::getValues));
DoubleSummaryStatistics statistics =
menu.stream().collect(Collectors.summarizingDouble(Dish::getValues));
LongSummaryStatistics statistics =
menu.stream().collect(Collectors.summarizingLong(Dish::getValues));
// 連接字符串,內部使用了StringBuilder
String shortMenu = menu.stream().map(Dish::getName).collect(joining());
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
// 一般通用的歸約, 要用Collectors.reducing工廠方法進行歸約
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));
// stream->collect->reducing->用法介紹,用於可變的歸約,適合並行操作
// reducing收集器永遠都不會返回 Optional.empty()
// 無初始值的情況,返回一個可以生成Optional結果的Collector
// 參數:op是核心函數,作用是如何處理兩個變量。
// 第一個變量是累積值,可以理解爲sum,第二個變量則是下一個要計算的元素。
// 類型:T是Stream裏的元素類型
// public static <T> Collector<T, ?, Optional<T>>reducing(BinaryOperator<T> op)
Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
Map<City, Optional<Person>> tallestByCity = personList.stream().collect(
groupingBy(
Person::getCity, reducing(BinaryOperator.maxBy(byHeight))
)
);
tallestByCity.forEach(
(k,v)->
System.out.println(
JSONObject.fromObject(k) + "=" + JSONObject.fromObject(v.get())
)
);
// 有初始值的情況,返回一個可以直接產生結果的Collector
// 參數:identity[返回類型的初始值]
// op是核心函數,作用是如何處理兩個變量。
// 第一個變量是累積值,可以理解爲sum,第二個變量則是下一個要計算的元素。
// 類型:T是Stream裏的元素類型
// public static <T> Collector<T, ?, Optional<T>>reducing(T identity,BinaryOperator<T> op
Map<City, Person> tallestByCity1 = personList.stream().collect(
groupingBy(Person::getCity, reducing(new Person(), BinaryOperator.maxBy(byHeight)))
);
tallestByCity1.forEach((k,v)->
System.out.println(JSONObject.fromObject(k) + "=" + JSONObject.fromObject(v)));
// 有初始值,還有針對元素的處理方案mapper,生成一個可以直接產生結果的Collector,
// 元素在執行結果操作op之前需要先執行mapper進行元素轉換操作
// 參數:
// identity[返回類型的初始值]
// mapper則是map的作用,意義在於將Stream流轉換成你想要的類型流
// op是核心函數,作用是如何處理兩個變量。
// 第一個變量是累積值,可以理解爲sum,第二個變量則是下一個要計算的元素。
// 類型:U 返回類型;T是Stream裏的元素類型;
// public static <T, U>Collector<T, ?, U> reducing(U identity,
// Function<? super T, ? extends U> mapper,BinaryOperator<U> op)
Comparator<String> byLength = Comparator.comparing(String::length);
Map<City, String> longestNameByCity = personList.stream().collect(
groupingBy(
Person::getCity, reducing("", Person::getLastName, BinaryOperator.maxBy(byLength))
)
);
longestNameByCity.forEach((k,v)->
System.out.println(JSONObject.fromObject(k) + "=" + v));
// 有初始值,迭代計算,返回結果值
// 參數:
// identity[返回類型的初始值]
// accumulator[迭代計算]
// 類型:T是Stream裏的元素類型
// T reduce(T identity, BinaryOperator<T> accumulator)
Integer sums = Stream.of(1, 2, 3, 4).reduce(0, (sum, item) -> sum + item);
System.out.println(sums);
// 分組,groupingBy,桶
Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));
// 結果
// {FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza],
// MEAT=[pork, beef, chicken]}
public enum CaloricLevel { DIET, NORMAL, FAT }
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} )
);
// 多級分組
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel =
menu.stream().collect(
groupingBy(Dish::getType, // 一級分組
groupingBy(dish -> { // 二級分組
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
})
)
);
// 結果
// {MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]},
// FISH={DIET=[prawns], NORMAL=[salmon]},
// OTHER={DIET=[rice, seasonal fruit], NORMAL=[french fries, pizza]}}
Map<Dish.Type, Long> typesCount =
menu.stream().collect(groupingBy(Dish::getType, counting()));
// 結果
// {MEAT=3, FISH=2, OTHER=4}
// 把收集器的結果轉換爲另一種類型
Map<Dish.Type, Dish> mostCaloricByType =
menu.stream().collect(
groupingBy(
Dish::getType,
collectingAndThen(maxBy(comparingInt(Dish::getCalories)),// 包裝後的收集器
Optional::get // 轉換函數,轉換包裝後的收集器的結果,返回最終的收集器
)
)
);
// 結果
// {FISH=salmon, OTHER=pizza, MEAT=pork}
// 聯合使用的其他收集器
Map<Dish.Type, Integer> totalCaloriesByType = menu.stream().collect(
groupingBy(
Dish::getType,
summingInt(Dish::getCalories)
)
);
Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = menu.stream().collect(
groupingBy(
Dish::getType,
mapping(
dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
},
toSet() // 此處可以讓你更多控制結果數據
)
)
);
// 結果
// {OTHER=[DIET, NORMAL], MEAT=[DIET, NORMAL, FAT], FISH=[DIET, NORMAL]}
// 分區是分組的特殊情況:由一個謂詞(返回一個布爾值的函數)作爲分類函數,它稱分區函數。
// 分區函數返回一個布爾值,得到的分組Map的鍵類型是Boolean,它最多可以分爲true是一組,false是一組。
// 簡單分區
Map<Boolean, List<Dish>> partitionedMenu = menu.stream().collect(
partitioningBy(Dish::isVegetarian) // 分區函數
);
// 結果
// {
// false=[pork, beef, chicken, prawns, salmon],
// true=[french fries, rice, season fruit, pizza]
// }
// 分區篩選
List<Dish> vegetarianDishes = menu.stream().filter(Dish::isVegetarian).collect(toList());
// 等價於
List<Dish> vegetarianDishes = partitionedMenu.get(true);
// 結果
// [french fries, rice, season fruit, pizza]
// 高級應用
Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType = menu.stream().collect(
partitioningBy(
Dish::isVegetarian,
groupingBy(Dish::getType) // 進一步處理
)
);
// 結果
// {false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]},
// true={OTHER=[french fries, rice, season fruit, pizza]}}
Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = menu.stream().collect(
partitioningBy(
Dish::isVegetarian,
collectingAndThen(
maxBy(comparingInt(Dish::getCalories)),
Optional::get
)
)
);
// 結果
// {false=pork, true=pizza}
// Collector 接口介紹
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
}
// 參數
// T 是流中要收集的項目的泛型
// A 是累加器的類型,累加器是在收集過程中用於累積部分結果的對象
// R 是收集操作得到的對象(通常但並不一定是集合)的類型
// 過程總覽
// 1.原始流會以遞歸方式拆分爲子流,直到定義流是否需要進一步拆分的一個條件爲非(如果分佈式工作單
// 位太小,並行計算往往比順序計算要慢,而且要是生成的並行任務比處理器內核數多很多的話就沒什麼用了
// 2.所有的子流都可以進行並行處理,進行歸約。
// 3.使用收集器combiner方法返回的函數,將所有的部分結果兩兩合併。這時會把原始流每次拆分時
// 得到的子流對應的結果合併起來。
// 自定義接口的實現
public class ToListCollector<T> implements Collector<T, List<T>, List<T>>
// 詳細介紹
// 前四個方法都會返回一個會被 collect 方法調用的函數
// 第五個方法 characteristics 則提供了一系列特徵,也就是一個提示列表,告訴 collect 方法在執行歸
// 約操作的時候可以應用哪些優化(比如並行化)
// 1.生產者:[建立新的結果容器] supplier()
// supplier 方法必須返回一個結果爲空的 Supplier ,也就是一個無參數函數,在調用時它會創建一個空的
// 累加器實例,供數據收集過程使用
// 舉例
public Supplier<List<T>> supplier() {
return () -> new ArrayList<T>();
}
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
// 2.累加器:[將元素添加到結果容器] accumulator()
// accumulator 方法會返回執行歸約操作的函數。
// 當遍歷到流中第n個元素時,這個函數執行時會有兩個參數:
// 保存歸約結果的累加器(已收集了流中的前 n1 個項目),還有第n個元素本身。
// 該函數將返回void,因爲累加器是原位更新,即函數的執行改變了它的內部狀態以體現遍歷的元素的效果。
// 舉例
public BiConsumer<List<T>, T> accumulator() {
return (list, item) -> list.add(item);
}
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}
// 3.轉換器:[對結果容器應用最終轉換] finisher()
// 在遍歷完流後,finisher方法必須返回在累積過程的最後要調用的一個函數,以便將累加器對象轉換爲
// 整個集合操作的最終結果。
// 舉例
public Function<List<T>, List<T>> finisher() {
return Function.identity();
}
// 4.完成器/組合器:[合併兩個結果容器] combiner() [並行時執行]
// 返回一個供歸約操作使用的函數,它定義了對流的各個子部分進行並行處理時,各個子部分歸約所得的累
// 加器要如何合併。
// 舉例
public BinaryOperator<List<T>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
}
}
5.characteristics()
// 返回一個不可變的 Characteristics 集合,它定義了收集器的行爲
// 1) UNORDERED: 歸約結果不受流中項目的遍歷和累積順序的影響
// 2) CONCURRENT: accumulator函數可以從多個線程同時調用,且該收集器可以並行歸約流。
// 如果收集器沒有標爲UNORDERED,那它僅在用於無序數據源時纔可以並行歸約。
// 3) IDENTITY_FINISH: 完成器方法返回的函數是一個恆等函數,可以跳過。
// 這種情況下,累加器對象將會直接用作歸約過程的最終結果。
public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {
@Override
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}
@Override
public Function<List<T>, List<T>> finisher() {
return Function.indentity();
}
@Override
public BinaryOperator<List<T>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
};
}
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT));
}
}
// 如果不實現Collector接口
List<Dish> dishes = menuStream.collect(
ArrayList::new, // 生產者
List::add, // 累加器
List::addAll // 完成器/組合器
);
// characteristics是IDENTITY_FINISH。
// 不易讀
// 不推薦這種寫法
// 自定義收集器,性能一般會有所提升
並行處理
// 並行數據處理與性能
// 並行流就是一個把內容分成多個數據塊,並用不同的線程分別處理每個數據塊的流。
// 舉例
public static long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1)
.limit(n)
.parallel() // 將流轉換爲並行流,之後進行的所有操作都並行執行
// .sequential() // 將流轉換爲順序流,之後進行的所有操作都順序執行
.reduce(0L, Long::sum);
}
// 解釋
// 並行流內部使用了默認的 ForkJoinPool,它默認的線程數量就是你的處理器數量,這個值是由
// Runtime.getRuntime().available-Processors() 得到的。
// 下面是改變線程池大小,這是一個全局設置,影響代碼中所有的並行流,強烈建議你不要修改它。
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
// 並行化過程本身需要對流做遞歸劃分,把每個子流的歸納操作分配到不同的線程,然後把這些操作的結果合
// 併成一個值。在多個內核之間移動數據的代價也可能比你想的要大,所以很重要的一點是要保證在內核中並
// 行執行工作的時間並行流比在內核之間傳輸數據的時間長。
// **很多情況下不可能或不方便並行化**
// 錯用並行流而產生錯誤的首要原因,就是使用的算法改變了某些共享狀態
// 舉例
public static long sideEffectSum(long n) {
Accumulator accumulator = new Accumulator();
LongStream.rangeClosed(1, n).forEach(accumulator::add);
return accumulator.total;
}
public class Accumulator {
public long total = 0;
public void add(long value) {
total += value; // 錯誤根源是不是一個原子操作,它會改變多個線程共享的對象的可變狀態
}
}
public static long sideEffectParallelSum(long n) {
Accumulator accumulator = new Accumulator();
LongStream.rangeClosed(1, n).parallel().forEach(accumulator::add);
return accumulator.total;
}
System.out.println("SideEffect parallel sum done in: " +
measurePerf(ParallelStreams::sideEffectParallelSum, 10_000_000L) +"msecs" );
// 每次執行的結果都不同
// 影響性能因素
// 1)裝箱,大大降低性能
// 2)操作本身在並行流上的性能就比順序流差:limit和findFirst等依賴於元素順序的操作,並行代價大。
// 可以調用unordered方法來把有序流變成無序流對無序並行流調用limit可能會比單個有序流更高效。
// 前提是順序不是主要因素
// 3)慮流的操作流水線的總計算成本,N*Q 是對成本的一個粗略的定性估計。
// N是要處理的元素的總數,Q是一個元素通過流水線的大致處理成本。
// Q值較高就意味着使用並行流時性能好的可能性比較大。
// 4)對於較小的數據量,儘量選擇順序流,並行流,並行會造成額外開銷。
// 5)考慮流背後的數據結構是否易於分解,易分解的適合並行流。
// 6)流自身的特點,以及流水線中的中間操作修改流的方式,都可能會改變分解過程的性能。
// 7)考慮終端操作中合併步驟的代價是大是小。
分支/合併框架
// 分支/合併框架, Java 7 引入
// 目的: 以遞歸方式將可以並行的任務拆分成更小的任務,然後將每個子任務的結果合併起來生成整體結果。
// 原理:實現ExecutorService接口,把子任務分配給線程池(稱爲 ForkJoinPool)中的工作線程。
// RecursiveTask
// 介紹
// 創建 RecursiveTask<R> 的一個子類,用於將任務提交 ForkJoinPool。
// R 是並行化任務(以及所有子任務)產生的結果類型。
// 如果任務不返回結果,則是 RecursiveAction 類型(當然它可能會更新其他非局部機構)。
// 定義
// protected abstract R compute();
// 邏輯,分治算法
// if (任務足夠小或不可分) {
// 順序計算該任務
// } else {
// 將任務分成兩個子任務
// 遞歸調用本方法,拆分每個子任務,等待所有子任務完成
// 合併每個子任務的結果
// }
// 注意
// ForkJoinPool一般來說把它實例化一次,然後把實例保存在靜態字段中,使之成爲單例
// Runtime.availableProcessors 的返回值來決定線程池使用的線程數
// 有效使用它的最佳做法
// 1)對一個任務調用 join 方法會阻塞調用方,直到該任務做出結果。因此,有必要在兩個子任務的計算
// 都開始之後再調用它。否則,你得到的版本會比原始的順序算法更慢更復雜,因爲每個子任務都必須
// 等待另一個子任務完成才能啓動。
// 2)不應該在 RecursiveTask 內部使用 ForkJoinPool 的 invoke 方法。相反,你應該始終直接調
// 用 compute 或 fork 方法,只有順序代碼才應該用 invoke 來啓動並行計算。
// 3)對子任務調用 fork 方法可以把它排進 ForkJoinPool 。同時對左邊和右邊的子任務調用它似乎很
// 自然,但這樣做的效率要比直接對其中一個調用 compute 低。這樣做你可以爲其中一個子任務重用
// 同一線程,從而避免在線程池中多分配一個任務造成的開銷。
// 4)調試使用分支/合併框架的並行計算可能有點棘手。特別是你平常都在你喜歡的IDE裏面看棧
// 跟蹤(stack trace)來找問題,但放在分支/合併計算上就不行了,因爲調用 compute
// 的線程並不是概念上的調用方,後者是調用 fork 的那個。
// 5)和並行流一樣,你不應理所當然地認爲在多核處理器上使用分支/合併框架就比順序計算快。
// 一個任務可以分解成多個獨立的子任務,才能讓性能在並行化時有所提升。
// 所有這些子任務的運行時間都應該比分出新任務所花的時間長;
// 一個慣用方法是把輸入/輸出放在一個子任務裏,計算放在另一個裏,這樣計算就可以和輸入/輸出同時
// 進行。
// 此外,在比較同一算法的順序和並行版本的性能時還有別的因素要考慮。就像任何其他Java代碼一樣,
// 分支/合併框架需要“預熱”或者說要執行幾遍纔會被JIT編譯器優化。這就是爲什麼在測量性能之前跑幾
// 遍程序很重要,測試框架就是這麼做的。同時,編譯器內置的優化可能會爲順序版本帶來一些優勢。
// 必須選擇一個標準,來決定任務是要進一步拆分還是已小到可以順序求值。
// 工作竊取(work stealing)
/*
在實際應用中,這意味着這些任務差不多被平均分配到 ForkJoinPool 中的所有線程上。
每個線程都爲分配給它的任務保存一個雙向鏈式隊列,每完成一個任務,就會從隊列頭上取出下一個任務開始執行。
基於前面所述的原因,某個線程可能早早完成了分配給它的所有任務,也就是它的隊列已經空了,而其他的線程還很忙。
這時,這個線程並沒有閒下來,而是隨機選了一個別的線程,從隊列的尾巴上“偷走”一個任務。
這個過程一直繼續下去,直到所有的任務都執行完畢,所有的隊列都清空。
這就是爲什麼要劃成許多小任務而不是少數幾個大任,這有助於更好地在工作線程之間平衡負載。
一般來說,這種工作竊取算法用於在池中的工作線程之間重新分配和平衡任務
*/
// Spliterator【可分迭代器(splitable iterator)】【java8 接口】
// 一種自動機制來爲你拆分流的機制,爲了並行執行而設計的。
public interface Spliterator<T> {
// 按順序一個一個使用 Spliterator 中的元素,並且如果還有其他元素要遍歷就返回 true
boolean tryAdvance(Consumer<? super T> action);
// 可以把一些元素劃出去分給第二個 Spliterator (由該方法返回),讓它們兩個並行處理。
Spliterator<T> trySplit();
// 估計還剩下多少元素要遍歷,即使不那麼確切,也能快速算出來是一個值有助於讓拆分均勻一點。
long estimateSize();
// 代表 Spliterator 本身特性集的編碼
int characteristics();
}
重構、測試和調試
// 重構、測試和調試
// 1.重構
// 重構代碼,用Lambda表達式取代匿名類
// 用方法引用重構Lambda表達式
// 用Stream API重構命令式的數據處理
// 匿名類和Lambda表達式中的 this 和 super 的含義是不同的
// 在匿名類中, this 代表的是類自身, 能屏蔽包含類的變量
// 在Lambda中, this 代表的是包含類, 不能屏蔽包含類的變量
// 1)匿名類轉Lambda表達式的注意點
interface Task{
public void execute();
}
public static void doSomething(Runnable r){ r.run(); }
public static void doSomething(Task a){ a.execute(); }
doSomething(new Task() {
public void execute() {
System.out.println("Danger danger!!");
}
});
// 使用顯式的類型轉換來解決這種模棱兩可的情況
doSomething((Task)() -> System.out.println("Danger danger!!"));
doSomething((Runnable)() -> System.out.println("Danger danger!!"));
// 2)Lambda轉方法引用的注意點
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel =
menu.stream().collect(
groupingBy(
dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}
)
);
// 方法引用
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel =
menu.stream().collect(groupingBy(Dish::getCaloricLevel));
public class Dish{
public CaloricLevel getCaloricLevel(){
if (this.getCalories() <= 400) return CaloricLevel.DIET;
else if (this.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}
}
// 儘量考慮使用靜態輔助方法,比如 comparing 、 maxBy。
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort(comparing(Apple::getWeight));
// 儘量考慮通用的歸約操作,比如 sum 、 maximum
int totalCalories = menu.stream().map(Dish::getCalories).reduce(0, (c1, c2) -> c1 + c2);
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
// 3)命令式的數據處理轉Stream的注意點
// 命令式代碼的模式:篩選和抽取
List<String> dishNames = new ArrayList<>();
for(Dish dish: menu){
if(dish.getCalories() > 300){
dishNames.add(dish.getName());
}
}
menu.parallelStream()
.filter(d -> d.getCalories() > 300)
.map(Dish::getName)
.collect(toList());
// 4)增加代碼的靈活性
// 4.1)有條件的延遲執行
if (logger.isLoggable(Log.FINER)){
logger.finer("Problem: " + generateDiagnostic());
}
// 出現的問題
// 日誌器的狀態(它支持哪些日誌等級)通過 isLoggable 方法暴露給了客戶端代碼。
// 爲什麼要在每次輸出一條日誌之前都去查詢日誌器對象的狀態?
public void log(Level level, Supplier<String> msgSupplier)
logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());
public void log(Level level, Supplier<String> msgSupplier){
if(logger.isLoggable(level)){
log(level, msgSupplier.get());
}
}
// 4.2)環繞執行
String oneLine = processFile((BufferedReader b) -> b.readLine());
String twoLines = processFile((BufferedReader b) -> b.readLine() + b.readLine());
public static String processFile(BufferedReaderProcessor p) throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
// 5)使用Lambda 重構面向對象的設計模式
// 5.1)策略模式
// 5.2)模板方法
// 5.3)策略模式
// 5.4)觀察者模式
// 5.5)責任鏈模式
// 5.6)工廠模式
默認方法
// 默認方法
// 類庫設計者,以兼容的方式改進API
public interface Sized {
int size();
default boolean isEmpty() {
return size() == 0;
}
}
// 默認方法的兩種用例:可選方法和行爲的多繼承
// 可選方法
interface Iterator<T> {
boolean hasNext();
T next();
default void remove() {
throw new UnsupportedOperationException();
}
}
// 行爲的多繼承
public interface Moveable {
int getX();
int getY();
void setX(int x);
void setY(int y);
default void moveHorizontally(int distance){
setX(getX() + distance);
}
default void moveVertically(int distance){
setY(getY() + distance);
}
}
// 組合接口
public class Monster implements Rotatable, Moveable, Resizable {
}
// 關於繼承的一些錯誤觀點
// 繼承不應該成爲你一談到代碼複用就試圖倚靠的萬精油。
// 聲明爲 final 的類不能被其他的類繼承,避免發生這樣的反模式,防止核心代碼的功能被污染。
// 解決衝突的規則
/**
如果一個類使用相同的函數簽名從多個地方(比如另一個類或接口)繼承了方法,通過三條規則可以進行判斷。
(1) 類中的方法優先級最高。類或父類中聲明的方法的優先級高於任何聲明爲默認方法的優先級。
(2) 如果無法依據第一條進行判斷,那麼子接口的優先級更高:函數簽名相同時,優先選擇擁有最具體實現的
默認方法的接口,即如果 B 繼承了 A ,那麼 B 就比 A 更加具體。
(3) 最後,如果還是無法判斷,繼承了多個接口的類必須通過顯式覆蓋和調用期望的方法,顯式地選擇使用哪
一個默認方法的實現。
**/
Optional
// Java語言的架構師Brian Goetz:
// Optional 的設計初衷僅僅是要支持能返回 Optional 對象的語法
// 用 Optional 取代 null
// null 帶來的種種問題
/**
1.它是錯誤之源
NullPointerException 是目前Java程序開發中最典型的異常。
2.它會使你的代碼膨脹
它讓你的代碼充斥着深度嵌套的 null 檢查,代碼的可讀性糟糕透頂。
3.它自身是毫無意義的
null自身沒有任何的語義,尤其是,它代表的是在靜態類型語言中以一種錯誤的方式對缺失變量值的模。
4.它破壞了Java的哲學
Java一直試圖避免讓程序員意識到指針的存在,唯一的例外是: null 指針。
5.它在Java的類型系統上開了個口子
null 並不屬於任何類型,這意味着它可以被賦值給任意引用類型的變量。這會導致問題,原因是當這個
變量被傳遞到系統中的另一個部分後,你將無法獲知這個 null 變量最初的賦值到底是什麼類型。
**/
// 應用 Optional 的幾種模式
// 1.創建 Optional 對象
// 1.1.聲明一個空的 Optional
Optional<Car> optCar = Optional.empty();
// 1.2.依據一個非空值創建 Optional,car對象不能爲 null
Optional<Car> optCar = Optional.of(car);
// 1.3.可接受 null 的 Optional
Optional<Car> optCar = Optional.ofNullable(car);
// 2.使用 map 從 Optional 對象中提取和轉換值
String name = null;
if(insurance != null){
name = insurance.getName();
}
// 等價
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
// 使用 flatMap 鏈接 Optional 對象
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}
// 等價
Optional<Person> optPerson = Optional.of(person);
Optional<String> name =
optPerson.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");// 如果 Optional 的結果值爲空,設置默認值
// Optional無法序列化的解決方案
public class Person {
private Car car;
public Optional<Car> getCarAsOptional() {
return Optional.ofNullable(car);
}
}
// 3.Optional的方法
/**
get() 是這些方法中最簡單但又最不安全的方法。如果變量存在,它直接返回封裝的變量值,否則就拋出一個
NoSuchElementException 異常。所以,除非你非常確定 Optional變量一定包含值,否則使用這個方法是個
相當糟糕的主意。此外,這種方式即便相對於嵌套式的 null 檢查,也並未體現出多大的改進。
orElse(T other) 允許你在Optional對象不包含值時提供一個默認值。
orElseGet(Supplier<? extends T> other) 是 orElse 方法的延遲調用版, Supplier方法只有在
Optional 對象不含值時才執行調用。如果創建默認值是件耗時費力的工作,你應該考慮採用這種方式(藉此提
升程序的性能),或者你需要非常確定某個方法僅在Optional 爲空時才進行調用,也可以考慮該方式(這種情
況有嚴格的限制條件)。
orElseThrow(Supplier<? extends X> exceptionSupplier) 和 get 方法非常類似,它們遭遇 Optional
對象爲空時都會拋出一個異常,但是使用 orElseThrow 你可以定製希望拋出的異常類型。
ifPresent(Consumer<? super T>) 讓你能在變量值存在時執行一個作爲參數傳入的方法,否則就不進行任何
操作。
**/
// 使用
// 不安全,容易發生null異常
public Optional<Insurance> nullSafeFindCheapestInsurance(
Optional<Person> person, Optional<Car> car) {
if (person.isPresent() && car.isPresent()) {
// get()容易發生null異常
return Optional.of(findCheapestInsurance(person.get(), car.get()));
} else {
return Optional.empty();
}
}
// 安全
public Optional<Insurance> nullSafeFindCheapestInsurance(
Optional<Person> person, Optional<Car> car) {
// 如果person爲null,則flatMap不會執行;如果car爲null,則返回一個空的 Optional 對象
return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
}
// 使用 filter 剔除特定的值
Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
System.out.println("ok");
}
//
Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance ->"CambridgeInsurance".equals(insurance.getName()))
.ifPresent(x -> System.out.println("ok"));
public String getCarInsuranceName(Optional<Person> person, int minAge) {
return person.filter(p -> p.getAge() >= minAge)
.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
// 優化
// 1.用 Optional 封裝可能爲 null 的值
Object value = map.get("key");
Optional<Object> value = Optional.ofNullable(map.get("key"));
// 2.異常與 Optional 的對比;OptionalUtility工具類
public static Optional<Integer> stringToInt(String s) {
try {
return Optional.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
CompletableFuture
// CompletableFuture: 組合式異步編程
// 參考文章:https://blog.csdn.net/u012549626/article/details/91572434
// Future 接口: 設計初衷是對將來某個時刻會發生的結果進行建模。易用。Java 5中被引入。
// 它建模了一種異步計算,返回一個執行運算結果的引用,當運算結束後,這個引用被返回給調用方。
// Future 接口的侷限性
// 1)將兩個異步計算合併爲一個——這兩個異步計算之間相互獨立,同時第二個又依賴於第一個的結果。
// 2)等待 Future 集合中的所有任務都完成。
// 3)僅等待 Future 集合中最快結束的任務完成(有可能因爲它們試圖通過不同的方式計算同一個值),
// 並返回它的結果。
// 4)通過編程方式完成一個 Future 任務的執行(即以手工設定異步操作結果的方式)。
// 5)應對 Future 的完成事件(即當 Future 的完成事件發生時會收到通知,並能使用 Future計算的結果
// 進行下一步的操作,不只是簡單地阻塞等待操作的結果)。
// CompletableFuture實現了Future<T>, CompletionStage<T>兩個接口 Java 8中被引入。
// 當一個Future可能需要顯示地完成時,使用CompletionStage接口去支持完成時觸發的函數和操作。
// 當兩個及以上線程同時嘗試完成、異常完成、取消一個CompletableFuture時,只有一個能成功。
// (1) CompletableFuture實現了CompletionStage接口的如下策略:
/**
1) 爲了完成當前的CompletableFuture接口或者其他完成方法的回調函數的線程,提供了非異步的完成操作。
2) 沒有顯式入參Executor的所有async方法都使用ForkJoinPool.commonPool()爲了簡化監視、調試和跟蹤,所有生成的異步任務都是標記接口AsynchronousCompletionTask的實例。
3) 所有的CompletionStage方法都是獨立於其他共有方法實現的,因此一個方法的行爲不會受到子類中其他方法的覆蓋
**/
// (2) CompletableFuture實現了Future接口的如下策略:
/**
1) CompletableFuture無法直接控制完成,所以cancel操作被視爲是另一種異常完成形式。方法isCompletedExceptionally可以用來確定一個CompletableFuture是否以任何異常的方式完成。
2) 以一個CompletionException爲例,方法get()和get(long,TimeUnit)拋出一個ExecutionException,對應CompletionException。爲了在大多數上下文中簡化用法,這個類還定義了方法join()和getNow,而不是直接在這些情況中直接拋出CompletionException。
**/
// CompletableFuture中4個異步執行任務靜態方法:
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
// 說明
// 其中supplyAsync用於有返回值的任務,runAsync則用於沒有返回值的任務。Executor參數可以手動指
// 定線程池,否則默認ForkJoinPool.commonPool()系統級公共線程池,
// 注意:這些線程都是Daemon線程,主線程結束Daemon線程不結束,只有JVM關閉時,生命週期終止。
// 方法介紹
// 1.創建任務
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
// 2.獲取執行結果
// 堵塞當前的線程,等待計算結果完成
V get();
// 堵塞當前的線程,可以設置等待的時間
V get(long timeout,Timeout unit);
// 當有了返回結果時會返回結果,如果異步線程拋了異常會返回自己設置的默認值(傳入的值).
T getNow(T defaultValue);
// 獲取執行結果,阻塞線程,等待計算結果
T join();
// get與join的區別:
// 拋出未經檢查的 CompletionException
V get() throws InterruptedException, ExecutionException;
// 不會拋出已檢查的異常,利用exceptionally()截獲異常
public T join()
// 舉例
try {
CompletableFuture.allOf(fanoutRequestList).get()
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
CompletableFuture<List<String>> cf = CompletableFuture
.supplyAsync(this::process)
.exceptionally(this::getFallbackListOfStrings) // CompletionException
.thenAccept(this::processFurther);
// 立即完成計算,並把結果設置爲傳的值,返回是否設置成功
// 如果 CompletableFuture 沒有關聯任何的Callback、異步任務等,如果調用get方法,那會一直阻塞下
// 去,可以使用complete方法主動完成計算
public boolean complete(T value)
// 立即完成計算,並拋出異常
public boolean completeExceptionally(Throwable ex)
// 3.當前任務正常完成以後執行,當前任務的執行結果可以作爲下一任務的輸入參數,無返回值.
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);
// 4.對不關心上一步的計算結果,執行下一個操作
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
// 5.當前任務正常完成以後執行,當前任務的執行的結果會作爲下一任務的輸入參數,有返回值
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)
// 6.結合兩個CompletionStage的結果,進行轉化後返回
// thenCombine(..) thenAcceptBoth(..) runAfterBoth(..)
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)
// 7.這個方法接收的輸入是當前的CompletableFuture的計算值,返回結果將是一個新的CompletableFuture
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)
// thenApply與thenCompose區別
thenApply:消費者,功能轉換
thenCompose:用來連接兩個CompletableFuture,返回值是一個新的CompletableFuture
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn) {
return uniApplyStage(null, fn);
}
public <U> CompletableFuture<U> thenCompose(
Function<? super T, ? extends CompletionStage<U>> fn) {
return uniComposeStage(null, fn);
}
// 8.執行兩個CompletionStage的結果,那個先執行完了,就是用哪個的返回值進行下一步操作
// applyToEither(..) acceptEither(..) runAfterEither(..)
public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn,Executor executor);
// 9.當運行出現異常時,調用該方法可進行一些補償操作,如設置默認值.
public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn);
// 10.當CompletableFuture的計算結果完成,或者拋出異常的時候,都可以進入whenComplete方法執行
public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,Executor executor);
// 11.當CompletableFuture的計算結果完成,或者拋出異常的時候,可以通過handle方法對結果進行處理
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);
// 12.allOf、anyOf
// allOf:當所有的CompletableFuture都執行完後執行計算
// anyOf:最快的那個CompletableFuture執行完之後執行計算
新的日期和時間API
參考表格
Github
附錄
A.術語
1.併發與並行
並行是指兩個或者多個事件在同一時刻發生;而併發是指兩個或多個事件在同一時間間隔發生。
並行是在不同實體上的多個事件,併發是在同一實體上的多個事件。
2.謂詞 Predicate
數學上,常用來代表一個類似函數的東西,接受一個參數值,並返回 true 或 false
3.外部迭代與內部迭代
外部迭代是手動代碼迭代,可以迭代過程,需要自己去迭代
內部迭代是迭代過程邏輯不可見,但是可以理解,不需要自己去迭代
4.Collection與Stream
Collection主要是存儲和訪問數據
Stream則主要對數據的計算,並行計算
5.匿名類
允許你同時聲明並實例化一個類,隨用隨建
佔用較多的內存空間
6.函數描述符
函數式接口的抽象方法的簽名基本上就是Lambda表達式的簽名。將這種抽象方法叫作函數描述符。
7.裝箱與拆箱 boxing and unboxing
裝箱:將原始類型轉換爲對應的引用類型的機制,保存到堆中。Predicate 可以避免裝箱
拆箱:將引用類型轉換爲對應的原始類型
8.實例變量、局部變量和類變量
本質不同:實例變量都存儲在堆中,局部變量保存在棧上,類變量保存方法區中
實例變量:定義在類中的變量成爲實例變量,又稱類的成員變量。具有默認的初始值,從屬於類由類生成對象時,才分配存儲空間,各對象間的實例變量互不干擾,能通過對象的引用來訪問實例變量。java多線程中,實例變量是多個線程的共享資源,在同步訪問時可能出現的問題。
類變量:類變量也稱靜態變量,用static關鍵字修飾。一個類的靜態變量,所有由這類生成的對象都共用這個類變量,類裝載時就分配存儲空間。一個對象修改了變量,則所以對象中這個變量的值都會發生改變。
局部變量:局部變量是方法中或者局部塊中聲明定義的變量或方法,沒有默認初始值,賦值後才能使用。
9.閉包
用科學的說法來說,閉包就是一個函數的實例,且它可以無限制地訪問那個函數的非本地變量。
可以認爲Lambda是對值封閉,而不是對變量封閉。
10.堆與棧
棧是在線程之間獨立的
函數中定義的基本類型變量,對象的引用變量都在函數的棧內存中分配。
棧內存特點,數數據一執行完畢,變量會立即釋放,節約內存空間。
棧內存中的數據,沒有默認初始化值,需要手動設置。
堆是在線程之間共享的
堆內存用來存放new創建的對象和數組。
堆內存中所有的實體都有內存地址值。
堆內存中的實體是用來封裝數據的,這些數據都有默認初始化值。
堆內存中的實體不再被指向時,JVM啓動垃圾回收機制,自動回收。
【待續】JAVA在程序運行時,在內存中劃分5片空間進行數據的存儲。分別是:寄存器、本地方法區、方法區、棧、堆【待續】
11.集合與流
集合【急切創建】
定義:一個內存中的數據結構,它包含數據結構中目前所有的值——集合中的每個元素都得先算出來才能添加到集合中。
特點:多次遍歷
流【延遲創建】
定義:在概念上固定的數據結構,其元素則是按需計算的。
特點:只能遍歷一次
12.map與flatMap
map 映射成一個流
flatMap 映射成流的內容,把一個流中的每個值都換成另一個流,然後把所有的流連接起來成爲一個流
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<String[]> b =
words.stream()
.map(w -> w.split(""))
.collect(Collectors.toList());
List<String> a =
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.collect(Collectors.toList());
13. findFirst與 findAny
findFirst 返回第一個元素,出現邏輯順序時用,並行限制多
findAny 返回當前流中的任意元素,不在乎順序,並行限制少
14.collect、reducing、reduce
reduce 根據一定的規則將Stream中的元素進行計算後返回一個唯一的值,終端操作(有狀態-有界)。適合不可變容器歸約
collect 收集器,把流歸約成一個集合,終端操作。適合可變容器歸約,適合並行
reducing 是一個收集器的歸約操作,參數爲BinaryOperator<T>,使用場景如下:
// 錯誤寫法
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
List<Integer> numbers = stream.reduce(
new ArrayList<Integer>(),
(List<Integer> l, Integer e) -> {
l.add(e);
return l;
},
(List<Integer> l1, List<Integer> l2) -> {
l1.addAll(l2);
return l1;
}
);
15.可讀性
別人理解這段代碼的難易程度
16.Lambda表達式特點
- 行爲參數化,表示不同的行爲
- 函數接口
- 重構代碼帶來的靈活行,有條件的延遲執行和環繞執行
- 簡潔
- 易懂,配合方法引用
17.抽象類和抽象接口之間的區別
一個類只能繼承一個抽象類,但是一個類可以實現多個接口。
一個抽象類可以通過實例變量(字段)保存一個通用狀態,而接口是不能有實例變量的。
18.null 引用和 Optional.empty()
從語義上,它們是一回事兒。
本質的區別:
解引用一個null, 一 定會觸 發 NullPointerException;
使用Optional.empty() 正常處理,它是 Optional 類的一個有效對象;
19.混聚mash-up
使用來自多個來源的內容,將這些內容聚合在一起,方便用戶的生活。
20.併發與並行
21.同步API與異步API
同步API[阻塞式調用]:你調用了某個方法,調用方在被調用方運行的過程中會等待,被調用方運行結束返回,調用方取得被調用方的返回值並繼續運行。使調用方和被調用方在不同的線程中運行,調用方還是需要等待被調用方結束運行。
異步API[非阻塞式調用]:會直接返回。在被調用方計算完成之前,將它剩餘的計算任務交給另一個線程去做,該線程和調用方是異步的。
22.調整線程池的大小
Brian Goetz建議,線程池大小與處理器的利用率之比可以使用下面的公式進行估算:
建議你將執行器使用的線程數,與你需要查詢的商店數目設定爲同一個值,這樣每個商店都應該對應一個服務線程。
23.並行——使用流還是 CompletableFutures
1.如果你進行的是計算密集型的操作,並且沒有I/O,那麼推薦使用 Stream 接口,因爲實現簡單,同時效率也可能是最高的
(如果所有的線程都是計算密集型的,那就沒有必要創建比處理器核數更多的線程)。
2.如果你並行的工作單元還涉及等待I/O的操作(包括網絡連接等待),那麼使用CompletableFuture 靈活性更好,你可以像前
文討論的那樣,依據等待/計算,或者W/C的比率設定需要使用的線程數。這種情況不使用並行流的另一個原因是,處理流的流
水線中如果發生I/O等待,流的延遲特性會讓我們很難判斷到底什麼時候觸發了等待。
24.Spliterator與Iterator
Spliterator爲了並行遍歷數據源中的元素而設計的迭代器,並行遍歷。
Iterator提供的順序遍歷迭代器,順序遍歷。
B.優化與拓展
1.將Collection轉成Stream,處理後再轉成Collection,提高效率,更加簡化
無副作用計算
放心地共享它,無需保留任何副本,並且由於它們不會被修改,還是線程安全的。
聲明式編程
要做什麼:你只需要使用不相互影響的表達式,描述想要做什麼,由系統來選擇如何實現。
函數式編程
它是一種使用函數進行編程的方式。
如果程序有一定的副作用,調用者不需要知道,或者完全不在意這些副作用,因爲這對它完全沒有影響。
數學函數
它接受零個或多個參數,生成一個或多個結果,並且不會有任何副作用。
純粹的函數式編程
無副作用計算,純數學函數。
"函數式"的函數或方法都只能修改本地變量。除此之外,它引用的對象都應該是不可變對象(final 類型)。
引用透明性
如果一個函數只要傳遞同樣的參數值,總是返回同樣的結果。
函數式方法不允許修改任何全局數據結構或者任何作爲參數傳入的參數。
尾-調優化(tail-call optimization)Java8 目前不支持
迭代調用發生在函數的最後。
尾-遞(tail-recursive):只用一個棧幀,實現遞歸,目前編譯器未做該優化。
使用Java 8進行編程時,建議儘量使用 Stream 取代迭代操作,從而避免變化帶來的影響。
一等函數(first-class function)
可以作爲參數傳遞,可以作爲返回值,還能存儲在數據結構中。能夠像普通變量一樣使用的函數。
高階函數(higher-order function)
滿足下面任一要求
1)接受至少一個函數作爲參數
2)返回的結果是一個函數
科裏化
它是一種可以幫助你模塊化函數、提高代碼重用性的技術。
它表示一種將一個帶有n元組參數的函數轉換成n個一元函數鏈的方法。——俄國數學家Moses Schönfinkel
理論定義
一種將具備2個參數的函數 f 轉化爲使用一個參數的函數 g,並且這個函數的返回值也是一個函數,它會作爲新函數的一個參數。
後者的返回值和初始函數的返回值相同,即 f(x,y) = (g(x))(y) 。
函數式,不會對原有數據結構進行改動。但可能會有很多副本的存在。
持久化數據結構
通過函數式方法/編程,保證數據結構的值始終保持一致,即,創建副本。
前提是不能修改數據源(final)。
Stream延遲計算
當你向一個 Stream發起一系列的操作請求時,這些請求只是被一一保存起來。只有當你向Stream發起一個終端操作時,纔會實際地進行計算。
模式匹配 Java8 目前不支持
Java中有switch,但是有限制byte、short、int 或者 char,String(java7引入)
Scala語言:一種混合了面向對象和函數式編程的語言
object Test {
def main(args: Array[String]) {
val alice = new Person("Alice", 25)
val bob = new Person("Bob", 32)
val charlie = new Person("Charlie", 32)
for (person <- List(alice, bob, charlie)) {
person match {
case Person("Alice", 25) => println("Hi Alice!")
case Person("Bob", 32) => println("Hi Bob!")
case Person(name, age) =>
println("Age: " + age + " year, name: " + name + "?")
}
}
}
// 樣例類
case class Person(name: String, age: Int)
}
結合器
高階函數接受兩個或多個函數,並返回另一個函數,實現的效果在某種程度上類似於將這些函數進行了結合。
C.參考代碼
1.Collector接口實現
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.IntStream;
import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH;
public class PrimeNumbersCollector
implements Collector<Integer, Map<Boolean, List<Integer>>, Map<Boolean, List<Integer>>> {
public static void main(String[] args) {
PrimeNumbersCollector primeNumbersCollector = new PrimeNumbersCollector();
int n = 9;
Map<Boolean, List<Integer>> result = IntStream.rangeClosed(2, n).boxed()
.collect(primeNumbersCollector);
result.forEach((k1,v1)->v1.forEach(v2->System.out.println(v2)));
System.out.println("--------------------------");
result = primeNumbersCollector.partitionPrimesWithCustomCollector(9);
result.forEach((k1,v1)->v1.forEach(v2->System.out.println(v2)));
}
// 除了實現接口外,還可以實現
public Map<Boolean, List<Integer>> partitionPrimesWithCustomCollector(int n) {
return IntStream.rangeClosed(2, n).boxed()
.collect(
() -> new HashMap<Boolean, List<Integer>>(2) {{
put(true, new ArrayList<>());
put(false, new ArrayList<>());
}},
(acc, candidate) -> {
acc.get(isPrime(acc.get(true), candidate) )
.add(candidate);
},
(map1, map2) -> {
map1.get(true).addAll(map2.get(true));
map1.get(false).addAll(map2.get(false));
});
}
/**
* 將找到的質數添加已經找到的質數列表
* @param sourceList 已經找到的質數列表
* @param candidate 判斷是否是質數
* @return boolean true質數,false非質數
* **/
private boolean isPrime(List<Integer> sourceList, int candidate) {
int candidateRoot = (int) Math.sqrt((double) candidate);
if (IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0)) {
return false;
}
return true;
}
@Override
public Supplier<Map<Boolean, List<Integer>>> supplier() {
return () -> new HashMap<Boolean, List<Integer>>() {{
put(true, new ArrayList<>());
put(false, new ArrayList<>());
}};
}
@Override
public BiConsumer<Map<Boolean, List<Integer>>, Integer> accumulator() {
return (Map<Boolean, List<Integer>> acc, Integer candidate) -> {
acc.get(isPrime(acc.get(true), candidate)).add(candidate);
};
}
@Override
public BinaryOperator<Map<Boolean, List<Integer>>> combiner() {
return (Map<Boolean, List<Integer>> map1,
Map<Boolean, List<Integer>> map2) -> {
map1.get(true).addAll(map2.get(true));
map1.get(false).addAll(map2.get(false));
return map1;
};
}
@Override
public Function<Map<Boolean, List<Integer>>,
Map<Boolean, List<Integer>>> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH));
}
}
2.CompletableFuture
import java.util.Random;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
try{
//futureTest();
//runAsync();
//supplyAsync();
//whenComplete();
//whenComplete2();
//thenAcceptTest();
//thenRunTest();
//thenApplyTest();
//thenCombineTest();
//thenAcceptBothTest();
//runAfterBothTest();
//thenComposeTest();
//applyToEitherTest();
//acceptEitherTest();
//exceptionallyTest();
//handleTest();
allOfAndAnyOfTest();
}catch (Exception ex) {
System.out.println(ex);
}
}
public static void futureTest() {
try{
// CompletableFuture實現了Future接口,因此你可以像Future那樣使用它。
//其次,CompletableFuture並非一定要交給線程池執行才能實現異步,你可以像下面這樣實現異步運行:
//如果發生異常:異常會被限制在執行任務的線程的範圍內,最終會殺死該線程,而這會導致等待get方法返回,結果的線程永久地被阻塞.
//[解]客戶端可以使用重載版本的get 方法,它使用一個超時參數來避免發生這樣的情況。這是一種值得推薦的做法,你應該儘量在你的代碼中添加超時判斷的邏輯,避免發生類似的問題。
CompletableFuture<String> completableFuture = new CompletableFuture<>();
new Thread(() -> {
// 模擬執行耗時任務
System.out.println("task doing...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 告訴completableFuture任務已經完成
completableFuture.complete("ok");
}).start();
// 獲取任務結果,如果沒有完成會一直阻塞等待
String result = completableFuture.get();
System.out.println("計算結果:" + result);
} catch (Exception e) {
System.out.println(e);
}
// 獲取任務線程內發生的異常
try{
CompletableFuture<String> completableFuture = new CompletableFuture<>();
new Thread(() -> {
// 模擬執行耗時任務
System.out.println("task doing...");
try {
Thread.sleep(3000);
int i = 1/0;
} catch (Exception e) {
// 告訴completableFuture任務發生異常了
completableFuture.completeExceptionally(e);
}
// 告訴completableFuture任務已經完成
completableFuture.complete("ok");
}).start();
// 獲取任務結果,如果沒有完成會一直阻塞等待
String result = completableFuture.get();
System.out.println("計算結果:" + result);
}catch (Exception ex) {
System.out.println(ex);
}
}
public static void thenAcceptTest(){
//當前任務正常完成以後執行,當前任務的執行結果可以作爲下一任務的輸入參數,無返回值.
//執行任務A,同時異步執行任務B,待任務B正常返回之後,用B的返回值執行任務C,任務C無返回值
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "任務A");
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> "任務B");
futureB.thenAccept(b -> {
System.out.println("參數:" + b);
System.out.println("執行任務C.");
});
}
public static void thenRunTest(){
//對不關心上一步的計算結果,執行下一個操作
//執行任務A,任務A執行完以後,執行任務B,任務B不接受任務A的返回值(不管A有沒有返回值),也無返回值
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "任務A");
futureA.thenRun(() -> System.out.println("執行任務B"));
}
public static void thenApplyTest(){
//當前任務正常完成以後執行,當前任務的執行的結果會作爲下一任務的輸入參數,有返回值
//多個任務串聯執行,下一個任務的執行依賴上一個任務的結果,每個任務都有輸入和輸出
//異步執行任務A,當任務A完成時使用A的返回結果resultA作爲入參進行任務B的處理,可實現任意多個任務的串聯執行
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> futureB = futureA.thenApply(s->s + " world");
CompletableFuture<String> futureC = futureB.thenApply(String::toUpperCase);
System.out.println(futureC.join());
// try {
// System.out.println(futureC.get());
// } catch (InterruptedException e) {
// e.printStackTrace();
// } catch (ExecutionException e) {
// e.printStackTrace();
// }
}
public static void thenCombineTest(){
//結合兩個CompletionStage的結果,進行轉化後返回
CompletableFuture<Double> futurePrice = CompletableFuture.supplyAsync(() -> 100d);
CompletableFuture<Double> futureDiscount = CompletableFuture.supplyAsync(() -> 0.8);
CompletableFuture<Double> futureResult = futurePrice.thenCombine(futureDiscount, (price, discount) -> price * discount);
System.out.println("最終價格爲:" + futureResult.join());
}
public static void thenAcceptBothTest(){
//結合兩個CompletionStage的結果,進行轉化
CompletableFuture<Double> futurePrice = CompletableFuture.supplyAsync(() -> 100d);
CompletableFuture<Double> futureDiscount = CompletableFuture.supplyAsync(() -> 0.8);
futurePrice.thenAcceptBoth(futureDiscount, (price, discount) -> {
System.out.println("最終價格爲:" + price * discount);
});
}
public static void runAfterBothTest(){
//結合兩個CompletionStage的結果,進行轉化後執行
CompletableFuture<Double> futurePrice = CompletableFuture.supplyAsync(() -> 100d);
CompletableFuture<Double> futureDiscount = CompletableFuture.supplyAsync(() -> 0.8);
System.out.println(futureDiscount.join());
futurePrice.runAfterBoth(futureDiscount, new Runnable() {
@Override
public void run() {
System.out.println("開始 run");
}
});
}
public static void thenComposeTest(){
//這個方法接收的輸入是當前的CompletableFuture的計算值,返回結果將是一個新的CompletableFuture
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> futureB = futureA.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " world"));
CompletableFuture<String> futureC = futureB.thenCompose(s -> CompletableFuture.supplyAsync(s::toUpperCase));
System.out.println(futureC.join());
}
public static void applyToEitherTest(){
//執行兩個CompletionStage的結果,那個先執行完了,就是用哪個的返回值進行下一步操作
//假設查詢商品a,有兩種方式,A和B,但是A和B的執行速度不一樣,我們希望哪個先返回就用那個的返回值.
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "通過方式A獲取商品a";
});
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "通過方式B獲取商品a";
});
CompletableFuture<String> futureC = futureA.applyToEither(futureB, product -> "結果:" + product);
System.out.println(futureC.join());
}
public static void acceptEitherTest(){
//執行兩個CompletionStage的結果,那個先執行完了,就是用哪個的返回值進行下一步操作
//假設查詢商品a,有兩種方式,A和B,但是A和B的執行速度不一樣,我們希望哪個先返回就用那個的返回值.
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "通過方式A獲取商品a";
});
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "通過方式B獲取商品a";
});
futureA.acceptEither(futureB, product -> {
System.out.println( "結果:" + product);
});
}
public static void exceptionallyTest(){
CompletableFuture<String> futureA = CompletableFuture.
supplyAsync(() -> "執行結果:" + (100 / 0))
.thenApply(s -> "futureA result:" + s)
.exceptionally(e -> {
System.out.println(e.getMessage());
return "futureA result: 100";
});
CompletableFuture<String> futureB = CompletableFuture.
supplyAsync(() -> "執行結果:" + 50)
.thenApply(s -> "futureB result:" + s)
.exceptionally(e -> "futureB result: 100");
System.out.println(futureA.join());
System.out.println(futureB.join());
}
public static void handleTest(){
//當CompletableFuture的計算結果完成,或者拋出異常的時候,可以通過handle方法對結果進行處理
CompletableFuture<String> futureA = CompletableFuture.
supplyAsync(() -> "執行結果:" + (100 / 0))
.thenApply(s -> "apply result:" + s)
.exceptionally(e -> {
System.out.println("ex:" + e.getMessage());
return "futureA result: 100";
})
.handle((s, e) -> {
if (e == null) {
System.out.println(s);
} else {
//未執行
System.out.println(e.getMessage());
}
return "handle result:" + (s == null ? "500" : s);
});
// 100
System.out.println(futureA.join());
CompletableFuture<String> futureB = CompletableFuture.
supplyAsync(() -> "執行結果:" + (100 / 0))
.thenApply(s -> "apply result:" + s)
.handle((s, e) -> {
if (e == null) {
//未執行
System.out.println(s);
} else {
System.out.println(e.getMessage());
}
return "handle result:" + (s == null ? "500" : s);
})
.exceptionally(e -> {
//未執行
System.out.println("ex:" + e.getMessage());
return "futureB result: 100";
});
// 500
System.out.println(futureB.join());
// handle和whenComplete的區別
// 1.都是對結果進行處理,handle有返回值,whenComplete沒有返回值
// 2.由於1的存在,使得handle多了一個特性,可在handle裏實現exceptionally的功能
}
public static void allOfAndAnyOfTest(){
//allOf:當所有的CompletableFuture都執行完後執行計算
//anyOf:最快的那個CompletableFuture執行完之後執行計算
//查詢一個商品詳情,需要分別去查商品信息,賣家信息,庫存信息,訂單信息等,這些查詢相互獨立,在不同的服務上,
//假設每個查詢都需要一到兩秒鐘,要求總體查詢時間小於2秒.
ExecutorService executorService = Executors.newFixedThreadPool(4);
long start = System.currentTimeMillis();
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "商品詳情";
},executorService);
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "賣家信息";
},executorService);
CompletableFuture<String> futureC = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "庫存信息";
},executorService);
CompletableFuture<String> futureD = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "訂單信息";
},executorService);
CompletableFuture<Void> allFuture = CompletableFuture.allOf(futureA, futureB, futureC, futureD);
allFuture.join();
System.out.println(futureA.join() + futureB.join() + futureC.join() + futureD.join());
System.out.println("總耗時:" + (System.currentTimeMillis() - start));
}
//無返回值
public static void runAsync() throws Exception {
System.out.println("------runAsync 無返回值------");
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println("run end ...");
});
future.get();
//future.join();
}
//有返回值
public static void supplyAsync() throws Exception {
System.out.println("------supplyAsync 有返回值------");
CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println("run end ...");
return System.currentTimeMillis();
});
long time = future.get();
//long time = future.join();
System.out.println("time = "+time);
}
// whenComplete 它可以處理正常的計算結果,或者異常情況。
public static void whenComplete() throws Exception {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println(e);
}
if(new Random().nextInt()%2>=0) {
int i = 12/0;
}
System.out.println("run end ...");
});
// 執行當前任務的線程執行繼續執行 whenComplete 的任務。
future.whenComplete(new BiConsumer<Void, Throwable>() {
@Override
public void accept(Void t, Throwable action) {
System.out.println("執行完成!");
}
});
future.exceptionally(new Function<Throwable, Void>() {
@Override
public Void apply(Throwable t) {
System.out.println("執行失敗!"+t.getMessage());
return null;
}
});
TimeUnit.SECONDS.sleep(2);
}
public static void whenComplete2() {
CompletableFuture<String> futureA = CompletableFuture.
supplyAsync(() -> "執行結果:" + (100 / 0))
.thenApply(s -> "apply result:" + s)
.whenComplete((s, e) -> {
if (s != null) {
//未執行
System.out.println(s);
}
if (e == null) {
//未執行
System.out.println(s);
} else {
System.out.println(e.getMessage());
}
})
.exceptionally(e -> {
System.out.println("ex"+e.getMessage());
return "futureA result: 100";
});
System.out.println(futureA.join());
}
}
3.拓展
import cn.example.improve.DefaultMethod.A;
import cn.example.improve.DefaultMethod.B;
import cn.example.improve.DefaultMethod.C;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.DoubleUnaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
// 函數式編程-求子集
System.out.println("-------函數式編程-求子集-------");
List<List<Integer>> subs = subsets(Arrays.asList(1, 4, 9));
subs.forEach(System.out::println);
// 科裏化-單位換算
System.out.println("-------科裏化-單位換算-------");
DoubleUnaryOperator convertCtoF = curriedConverter(9.0/5, 32);
DoubleUnaryOperator convertUSDtoGBP = curriedConverter(0.6, 0);
DoubleUnaryOperator convertKmtoMi = curriedConverter(0.6214, 0);
System.out.println(convertCtoF.applyAsDouble(24));
System.out.println(convertUSDtoGBP.applyAsDouble(100));
System.out.println(convertKmtoMi.applyAsDouble(20));
DoubleUnaryOperator convertFtoC = expandedCurriedConverter(-32, 5.0/9, 0);
System.out.println(convertFtoC.applyAsDouble(98.6));
System.out.println(converter(1,2,3));
// 函數式方法-二叉樹
System.out.println("-------函數式方法-二叉樹-------");
Tree t = new Tree("Mary", 22,
new Tree("Emily", 20,
new Tree("Alan", 50, null, null),
new Tree("Georgie", 23, null, null)
),
new Tree("Tian", 29,
new Tree("Raoul", 23, null, null),
null
)
);
System.out.println(lookup("Raoul", -1, t));
Tree f = fupdate("Jeff", 80, t);
System.out.println(lookup("Jeff", -1, f));
Tree u = update("Jim", 40, t);
System.out.println(lookup("Jim", -1, u));
// Stream自定義延遲列表
System.out.println("------Stream自定義延遲列表-------");
MyList<Integer> l = new MyLinkedList<>(5, new MyLinkedList<>(10, new Empty<>()));
System.out.println(l.head());
LazyList<Integer> numbers = from(2);
int two = numbers.head();
int three = numbers.tail().head();
int four = numbers.tail().tail().head();
System.out.println(two + " " + three + " " + four);
numbers = from(2);
int prime_two = primes(numbers).head();
int prime_three = primes(numbers).tail().head();
int prime_five = primes(numbers).tail().tail().head();
System.out.println(prime_two + " " + prime_three + " " + prime_five);
// 模式匹配
System.out.println("------模式匹配[Java8不支持]-------");
simplify();
Expr e = new BinOp("+", new Number(5), new BinOp("*", new Number(3), new Number(4)));
Integer result = evaluate(e);
System.out.println(e + " = " + result);
// 結合器
System.out.println("------結合器-------");
System.out.println(repeat(1, (Integer x) -> 2 * x).apply(10));
System.out.println(repeat(2, (Integer x) -> 2 * x).apply(10));
System.out.println(repeat(3, (Integer x) -> 2 * x).apply(10));
}
// 函數式編程---------------------------------------------------------------------------
public static List<List<Integer>> subsets(List<Integer> l) {
if (l.isEmpty()) {
List<List<Integer>> ans = new ArrayList<>();
ans.add(Collections.emptyList());
return ans;
}
Integer first = l.get(0);
List<Integer> rest = l.subList(1,l.size());
List<List<Integer>> subans = subsets(rest);
List<List<Integer>> subans2 = insertAll(first, subans);
return concat(subans, subans2);
}
public static List<List<Integer>> insertAll(Integer first, List<List<Integer>> lists) {
List<List<Integer>> result = new ArrayList<>();
for (List<Integer> l : lists) {
List<Integer> copyList = new ArrayList<>();
copyList.add(first);
copyList.addAll(l);
result.add(copyList);
}
return result;
}
public static List<List<Integer>> concat(List<List<Integer>> a, List<List<Integer>> b) {
List<List<Integer>> r = new ArrayList<>(a);
r.addAll(b);
return r;
}
// 科裏化-------------------------------------------------------------------------------
public static double converter(double x, double y, double z) {
return x * y + z;
}
public static DoubleUnaryOperator curriedConverter(double y, double z) {
return (double x) -> x * y + z;
}
public static DoubleUnaryOperator expandedCurriedConverter(double w, double y, double z) {
return (double x) -> (x + w) * y + z;
}
// 函數式方法-二叉樹---------------------------------------------------------------------
static class Tree {
private String key;
private int val;
private Tree left, right;
public Tree(String k, int v, Tree l, Tree r) {
key = k;
val = v;
left = l;
right = r;
}
}
// 命令式更新值,共享同一份數據結構
public static Tree update(String k, int newval, Tree t) {
if (t == null) {
t = new Tree(k, newval, null, null);
} else if (k.equals(t.key)) {
t.val = newval;
} else if (k.compareTo(t.key) < 0) {
t.left = update(k, newval, t.left);
} else {
t.right = update(k, newval, t.right);
}
return t;
}
// 函數式更新值,純函數式
// 對樹結構進行更新時,現存數據結構不會被破壞
public static Tree fupdate(String k, int newval, Tree t) {
return (t == null) ? new Tree(k, newval, null, null) :
k.equals(t.key) ? new Tree(k, newval, t.left, t.right) :
k.compareTo(t.key) < 0 ? new Tree(t.key, t.val, fupdate(k,newval, t.left), t.right) :
new Tree(t.key, t.val, t.left, fupdate(k,newval, t.right));
}
// 遞歸查詢值
public static int lookup(String k, int defaultval, Tree t) {
if (t == null) {
return defaultval;
}
if (k.equals(t.key)) {
return t.val;
}
return lookup(k, defaultval, k.compareTo(t.key) < 0 ? t.left : t.right);
}
// Stream自定義延遲列表----------------------------------------------------------------
public interface MyList<T> {
T head();
MyList<T> tail();
default boolean isEmpty() {
return true;
}
MyList<T> filter(Predicate<T> p);
}
public static class MyLinkedList<T> implements MyList<T> {
final T head;
final MyList<T> tail;
public MyLinkedList(T head, MyList<T> tail) {
this.head = head;
this.tail = tail;
}
@Override
public T head() {
return head;
}
@Override
public MyList<T> tail() {
return tail;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public MyList<T> filter(Predicate<T> p) {
return isEmpty() ? this : p.test(head()) ? new MyLinkedList<>(
head(), tail().filter(p)) : tail().filter(p);
}
}
public static class Empty<T> implements MyList<T> {
@Override
public T head() {
throw new UnsupportedOperationException();
}
@Override
public MyList<T> tail() {
throw new UnsupportedOperationException();
}
@Override
public MyList<T> filter(Predicate<T> p) {
return this;
}
}
public static class LazyList<T> implements MyList<T> {
final T head;
final Supplier<MyList<T>> tail;
public LazyList(T head, Supplier<MyList<T>> tail) {
this.head = head;
this.tail = tail;
}
@Override
public T head() {
return head;
}
@Override
public MyList<T> tail() {
// 觸發延遲列表( LazyList )的節點創建,體現延遲計算的特定
return tail.get();
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public MyList<T> filter(Predicate<T> p) {
return isEmpty() ? this : p.test(head()) ? new LazyList<>(head(), () -> tail().filter(p)) : tail().filter(p);
}
}
public static LazyList<Integer> from(int n) {
return new LazyList<>(n, () -> from(n + 1));
}
public static MyList<Integer> primes(MyList<Integer> numbers) {
return new LazyList<>(
numbers.head(), () -> primes(numbers.tail().filter(n -> n % numbers.head() != 0))
);
}
public static <T> void printAll(MyList<T> numbers) {
if (numbers.isEmpty()) {
return;
}
System.out.println(numbers.head());
printAll(numbers.tail());
}
// 模式匹配-----------Java8不支持------自動匹配操作符號-----------------------------------
private static void simplify() {
TriFunction<String, Expr, Expr, Expr> binopcase =
(opname, left, right) -> {
if ("+".equals(opname)) {
if (left instanceof Number && ((Number) left).val == 0) {
return right;
}
if (right instanceof Number && ((Number) right).val == 0) {
return left;
}
}
if ("*".equals(opname)) {
if (left instanceof Number && ((Number) left).val == 1) {
return right;
}
if (right instanceof Number && ((Number) right).val == 1) {
return left;
}
}
return new BinOp(opname, left, right);
};
Function<Integer, Expr> numcase = val -> new Number(val);
Supplier<Expr> defaultcase = () -> new Number(0);
Expr e = new BinOp("+", new Number(5), new Number(0));
Expr match = patternMatchExpr(e, binopcase, numcase, defaultcase);
if (match instanceof Number) {
System.out.println("Number: " + match);
} else if (match instanceof BinOp) {
System.out.println("BinOp: " + match);
}
}
private static Integer evaluate(Expr e) {
Function<Integer, Integer> numcase = val -> val;
Supplier<Integer> defaultcase = () -> 0;
TriFunction<String, Expr, Expr, Integer> binopcase =
(opname, left, right) -> {
if ("+".equals(opname)) {
if (left instanceof Number && right instanceof Number) {
return ((Number) left).val + ((Number) right).val;
}
if (right instanceof Number && left instanceof BinOp) {
return ((Number) right).val + evaluate((BinOp) left);
}
if (left instanceof Number && right instanceof BinOp) {
return ((Number) left).val + evaluate((BinOp) right);
}
if (left instanceof BinOp && right instanceof BinOp) {
return evaluate((BinOp) left) + evaluate((BinOp) right);
}
}
if ("*".equals(opname)) {
if (left instanceof Number && right instanceof Number) {
return ((Number) left).val * ((Number) right).val;
}
if (right instanceof Number && left instanceof BinOp) {
return ((Number) right).val * evaluate((BinOp) left);
}
if (left instanceof Number && right instanceof BinOp) {
return ((Number) left).val * evaluate((BinOp) right);
}
if (left instanceof BinOp && right instanceof BinOp) {
return evaluate((BinOp) left) * evaluate((BinOp) right);
}
}
return defaultcase.get();
};
return patternMatchExpr(e, binopcase, numcase, defaultcase);
}
public static class Expr {
}
public static class Number extends Expr {
int val;
public Number(int val) {
this.val = val;
}
@Override
public String toString() {
return "" + val;
}
}
public static class BinOp extends Expr {
String opname;
Expr left, right;
public BinOp(String opname, Expr left, Expr right) {
this.opname = opname;
this.left = left;
this.right = right;
}
@Override
public String toString() {
return "(" + left + " " + opname + " " + right + ")";
}
}
public static <T> T MyIf(boolean b, Supplier<T> truecase, Supplier<T> falsecase) {
return b ? truecase.get() : falsecase.get();
}
public static interface TriFunction<S, T, U, R> {
R apply(S s, T t, U u);
}
public static <T> T patternMatchExpr(Expr e,
TriFunction<String, Expr, Expr, T> binopcase,
Function<Integer, T> numcase, Supplier<T> defaultcase) {
if (e instanceof BinOp) {
return binopcase.apply(((BinOp) e).opname, ((BinOp) e).left, ((BinOp) e).right);
} else if (e instanceof Number) {
return numcase.apply(((Number) e).val);
} else {
return defaultcase.get();
}
}
// 結合器-------------------------------------------------------------------------------
// Function<T, R>
// R apply(T t);
public static <A, B, C> Function<A, C> compose(Function<B, C> g, Function<A, B> f) {
return x -> g.apply(f.apply(x));
}
public static <A> Function<A, A> repeat(int n, Function<A, A> f) {
return n == 0 ? x -> x : compose(f, repeat(n - 1, f));
// return n == 0 ? x -> x : repeat(n - 1, f).andThen(f);
}
}
結合器說明 | |||||
n | f | g | f.apply(x) | g.apply(f.apply(x)) | 初始值 |
3 | x -> x | (Integer x) -> 2 * x | 40=40 | 2*40=80 | 10 |
2 | x -> x | (Integer x) -> 2 * x | 20=20 | 2*20=40 | |
1 | x -> x | (Integer x) -> 2 * x | 10=10 | 2*10=20 | |
0 | x -> x | 未執行 | 10=10 |