Java8實戰


 

第 1 章 為什么要關心 Java 8

1.1 Java 怎么還在變
  1.1.1 Java 在編程語言生態系統中的位置
  1.1.2 流處理 

    流是一系列數據項,一次只生成一項
  1.1.3 用行為參數化把代碼傳遞給方法
  1.1.4 並行與共享的可變數據
  1.1.5 Java 需要演變
1.2 Java 中的函數
  1.2.1 方法和 Lambda 作為一等公民

  1.2.2 傳遞代碼:一個例子

  1.2.3 從傳遞方法到 Lambda

1.3 流

  

  Collection主要是為了存儲和訪問數據,而Stream則主要用於描述對數據的計算。

  順序處理

import static java.util.stream.Collectors.toList; 
List<Apple> heavyApples = 
 inventory.stream().filter((Apple a) -> a.getWeight() > 150) 
 .collect(toList());

  並行處理:

import static java.util.stream.Collectors.toList; 
List<Apple> heavyApples = 
 inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150) 
 .collect(toList());

1.4 默認方法

  接口中的默認方法,實現類可以不用實現

default void sort(Comparator<? super E> c) { 
 Collections.sort(this, c); 
}

1.5 來自函數式編程的其他好思想
1.6 小結

   函數是一等值;記得方法如何作為函數式值來傳遞,還有Lambda是怎樣寫的。
  Java 8中Streams的概念使得Collections的許多方面得以推廣,讓代碼更為易讀,並允許並行處理流元素。
  你可以在接口中使用默認方法,在實現類沒有實現方法時提供方法內容。
  其他來自函數式編程的有趣思想,包括處理null和使用模式匹配

第 2 章 通過行為參數化傳遞代碼

2.1 應對不斷變化的需求

  2.1.1 初試牛刀:篩選綠蘋果

public static List<Apple> filterGreenApples(List<Apple> inventory) { 
 List<Apple> result = new ArrayList<Apple>(); 
 for(Apple apple: inventory){ 
 if( "green".equals(apple.getColor() ) { 
 result.add(apple); 
 } 
 } 
 return result; 
}

  2.1.2 再展身手:把顏色作為參數 

public static List<Apple> filterApplesByColor(List<Apple> inventory, 
 String color) { 
 List<Apple> result = new ArrayList<Apple>(); 
 for (Apple apple: inventory){ 
 if ( apple.getColor().equals(color) ) { 
 result.add(apple); 
 } 
 } 
 return result; 
}

  2.1.3 第三次嘗試:對你能想到的每個屬性做篩選

public static List<Apple> filterApples(List<Apple> inventory, String color, 
 int weight, boolean flag) { 
 List<Apple> result = new ArrayList<Apple>(); 
 for (Apple apple: inventory){ 
 if ( (flag && apple.getColor().equals(color)) ||
 (!flag && apple.getWeight() > weight) ){
 result.add(apple); 
 } 
 } 
 return result; 
}

2.2 行為參數化 

  對選擇標准建模:

public interface ApplePredicate{ 
 boolean test (Apple apple); 
}

  用ApplePredicate的多個實現代表不同的選擇標准

public class AppleHeavyWeightPredicate implements ApplePredicate{ 
 public boolean test(Apple apple){ 
 return apple.getWeight() > 150; 
 } 
} 
public class AppleGreenColorPredicate implements ApplePredicate{
 public boolean test(Apple apple){ 
 return "green".equals(apple.getColor()); 
 } 
}

  策略圖

  

  行為參數化:讓方法接受多種行為(或戰略)作為參數,並在內部使用,來完成不同的行為

  第四次嘗試:根據抽象條件篩選

public static List<Apple> filterApples(List<Apple> inventory, 
 ApplePredicate p){ 
 List<Apple> result = new ArrayList<>(); 
 for(Apple apple: inventory){ 
 if(p.test(apple)){ 
 result.add(apple); 
 } 
 } 
 return result; 
}

  找出所有重量超過150克的紅蘋果,

public class AppleRedAndHeavyPredicate implements ApplePredicate{ 
 public boolean test(Apple apple){ 
 return "red".equals(apple.getColor()) 
 && apple.getWeight() > 150; 
 } 
} 
List<Apple> redAndHeavyApples = 
 filterApples(inventory, new AppleRedAndHeavyPredicate());

  參數化filterApples的行為,並傳遞不同的篩選策略

  參數化filterApples的行為並傳遞不同的篩選策略

2.3 對付啰嗦

  行為參數化:用謂詞篩選蘋果

public class AppleHeavyWeightPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}

public class AppleGreenColorPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
}

public class FilteringApples {
    public static void main(String... args) {
        List<Apple> inventory = Arrays.asList(new Apple(80, "green"),
                new Apple(155, "green"),
                new Apple(120, "red"));
        List<Apple> heavyApples =
                filterApples(inventory, new AppleHeavyWeightPredicate());
        List<Apple> greenApples =
                filterApples(inventory, new AppleGreenColorPredicate());
    }

    public static List<Apple> filterApples(List<Apple> inventory,
                                           ApplePredicate p) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory) {
            if (p.test(apple)) {
                result.add(apple);
            }
        }
        return result;
    }
}

  2.3.1 匿名類
  2.3.2 第五次嘗試:使用匿名類

List<Apple> redApples=filterApples(inventory,new ApplePredicate(){
    public boolean test(Apple apple){
        return"red".equals(apple.getColor());
    }
});

  2.3.3 第六次嘗試:使用 Lambda 表達式

List<Apple> result=filterApples(inventory,(Apple apple)->"red".equals(apple.getColor()));

    行為參數化與值參數化

  2.3.4 第七次嘗試:將 List 類型抽象化

public interface Predicate<T> {
    boolean test(T t);
}

public static <T> List<T> filter(List<T> list, Predicate<T> p) {
    List<T> result = new ArrayList<>();
    for (T e : list) {
        if (p.test(e)) {
            result.add(e);
        }
    }
    return result;
}

  用在香蕉、桔子、Integer或是String的列表上

List<Apple> redApples=filter(inventory,(Apple apple)->"red".equals(apple.getColor()));
List<Integer> evenNumbers=filter(numbers,(Integer i)->i%2==0);

2.4 真實的例子
  2.4.1 用 Comparator 來排序 .............. 31

    接口

public interface Comparator<T> {
    public int compare(T o1, T o2);
}

    按照重量升序對庫存排序

inventory.sort(new Comparator<Apple>(){
public int compare(Apple a1,Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
});

    用Lambda表達式

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

  2.4.2 用 Runnable 執行代碼塊

    原來

Thread t = new Thread(new Runnable() {
public void run(){
        System.out.println("Hello world");
    }
});

    現在

Thread t = new Thread(() -> System.out.println("Hello world"));

  2.4.3 GUI 事件處理

  2.5 小結 

    行為參數化,就是一個方法接受多個不同的行為作為參數,並在內部使用它們,完成不同行為的能力

    Java API包含很多可以用不同行為進行參數化的方法,包括排序、線程和GUI處理

第 3 章 Lambda 表達式

3.1 Lambda 管中窺豹

  匿名——我們說匿名,是因為它不像普通的方法那樣有一個明確的名稱:寫得少而想得多!
  函數——我們說它是函數,是因為Lambda函數不像方法那樣屬於某個特定的類。但和方法一樣,Lambda有參數列表、函數主體、返回類型,還可能有可以拋出的異常列表。
  傳遞——Lambda表達式可以作為參數傳遞給方法或存儲在變量中。
  簡潔——無需像匿名類那樣寫很多模板代碼

  有效的表達式

  案例

3.2 在哪里以及如何使用 Lambda
  3.2.1 函數式接口

    函數式接口就是只定義一個抽象方法的接口

    Lambda表達式是函數式接口一個具體實現的實例

  3.2.2 函數描述符

    函數式接口的抽象方法的簽名基本上就是Lambda表達式的簽名。我們將這種抽象方法叫作函數描述符

3.3 把 Lambda 付諸實踐:環繞執行模式

  是打開一個資源,做一些處理,然后關閉資源

  3.3.1 第 1 步:記得行為參數化

String result = processFile((BufferedReader br) ->br.readLine() + br.readLine());

  3.3.2 第 2 步:使用函數式接口來傳遞行為

    接口

@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}

    使用

public static String processFile(BufferedReaderProcessor p) throws IOException {
    …
}

  3.3.3 第 3 步:執行一個行為

public static String processFile(BufferedReaderProcessor p) throws IOException {
    try (BufferedReader br =new BufferedReader(new FileReader("data.txt"))) {
        return p.process(br);
    }
}

  3.3.4 第 4 步:傳遞 Lambda

String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());

  使pocessFile方法更靈活的四個步驟

3.4 使用函數式接口
  3.4.1 Predicate

@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);

  3.4.2 Consumer

    定義了一個名叫accept的抽象方法,它接受泛型T的對象,沒有返回(void)。你如果需要訪問類型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));

  3.4.3 Function

    定義了一個叫作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());  

  裝箱后的值本質上就是把原始類型包裹起來,並保存在堆里。因此,裝箱后的值需要更多的內存,並需要額外的內存搜索來獲取被包裹的原始值

  常用函數式接口

  示例

 

3.5 類型檢查、類型推斷以及限制
  3.5.1 類型檢查

    上下文(比如,接受它傳遞的方法的參數,或接受它的值的局部變量)中Lambda表達式需要的類型稱為目標類型

    解讀Lambda表達式的類型檢查過程

  3.5.2 同樣的 Lambda,不同的函數式接口

    同一個Lambda表達式就可以與不同的函數式接口聯系起來,只要它們的抽象方法簽名能夠兼容

Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;

  3.5.3 類型推斷

  3.5.4 使用局部變量

    Lambda捕獲了portNumber變量

int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);

    錯誤的使用

3.6 方法引用

  示例

  3.6.1 管中窺豹

    示例

    三種不同類型的Lambda表達式構建方法引用的辦法

  3.6.2 構造函數引用

3.7 Lambda 和方法引用實戰
  3.7.1 第 1 步:傳遞代碼

public class AppleComparator implements Comparator<Apple> {
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
} 
inventory.sort(new AppleComparator());

  3.7.2 第 2 步:使用匿名類

inventory.sort(new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
   }
});

  3.7.3 第 3 步:使用 Lambda 表達式 .... 58

import static java.util.Comparator.comparing; 
inventory.sort(comparing((a) -> a.getWeight()));

  3.7.4 第 4 步:使用方法引用 ............... 59

inventory.sort(comparing(Apple::getWeight));

3.8 復合 Lambda 表達式的有用方法
  3.8.1 比較器復合

    1. 逆序

inventory.sort(comparing(Apple::getWeight).reversed());

    2. 比較器鏈

  3.8.2 謂詞復合

  3.8.3 函數復合

    用compose的話,它將意味着f(g(x)),而andThen則意味着g(f(x)):

    andThen和compose之間的區別

3.9 數學中的類似思想
  3.9.1 積分
  3.9.2 與 Java 8 的 Lambda 聯系起來

public double integrate(DoubleFunction<Double> f, double a, double b) {
   return (f.apply(a) + f.apply(b)) * (b-a) / 2.0;
}

  3.10 小結

第 4 章 引入流

4.1 流是什么

  流是Java API的新成員,它允許你以聲明性方式處理數據集合

    將流操作鏈接起來構成流的流水線

4.2 流簡介

  元素序列——就像集合一樣,流也提供了一個接口,可以訪問特定元素類型的一組有序值

  源——流會使用一個提供數據的源,如集合、數組或輸入/輸出資源

  數據處理操作——流的數據處理功能支持類似於數據庫的操作,以及函數式編程語言中的常用操作

  流水線——很多流操作本身會返回一個流,這樣多個操作就可以鏈接起來,形成一個大的流水線

  內部迭代——與使用迭代器顯式迭代的集合不同,流的迭代操作是在背后進行的

  使用流來篩選菜單,找出三個高熱量菜餚的名字

4.3 流與集合

  集合與流之間的差異就在於什么時候進行計算

  流與集合

  4.3.1 只能遍歷一次

    和迭代器類似,流只能遍歷一次。遍歷完之后,我們就說這個流已經被消費掉了

  4.3.2 外部迭代與內部迭代

    區別

    內部迭代與外部迭代

4.4 流操作
  4.4.1 中間操作

    可以連接起來的流操作稱為中間操作,關閉流的操作稱為終端操作

  4.4.2 終端操作

    終端操作會從流的流水線生成結果。其結果是任何不是流的值,比如List、Integer,甚至void

  4.4.3 使用流

  4.5 小結

第 5 章 使用流 ........................................ 82

5.1 篩選和切片
  5.1.1 用謂詞篩選

  5.1.2 篩選各異的元素

  5.1.3 截短流

  5.1.4 跳過元素

5.2 映射

  5.2.1 對流中每一個元素應用函數

    流支持map方法,它會接受一個函數作為參數。這個函數會被應用到每個元素上,並將其映射成一個新的元素(使用映射一詞,是因為它和轉換類似,但其中的細微差別在於它是“創建一個新版本”而不是去“修改”)

  5.2.2 流的扁平化

    1. 嘗試使用map和Arrays.stream()

    2. 使用flatMap

    

    flatmap方法讓你把一個流中的每個值都換成另一個流,然后把所有的流連接起來成為一個流

5.3 查找和匹配
  5.3.1 檢查謂詞是否至少匹配一個元素

    anyMatch方法返回一個boolean,因此是一個終端操作

if(menu.stream().anyMatch(Dish::isVegetarian)){
    System.out.println("The menu is (somewhat) vegetarian friendly!!");
}

  5.3.2 檢查謂詞是否匹配所有元素

    allMatch流中的元素是否都能匹配

boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);

    noneMatch流中沒有任何元素與給定的謂詞匹配

boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);

  5.3.3 查找元素

    findAny方法將返回當前流中的任意元素
  5.3.4 查找第一個元素

List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> firstSquareDivisibleByThree =
        someNumbers.stream()
        .map(x -> x * x)
        .filter(x -> x % 3 == 0)
        .findFirst(); // 9

5.4 歸約
  5.4.1 元素求和

int sum = numbers.stream().reduce(0, (a, b) -> a + b);

  

    靜態的sum方法來對兩個數求和

int sum = numbers.stream().reduce(0, Integer::sum);

  5.4.2 最大值和最小值

    中間操作和終端操作

5.5 付諸實踐
  5.5.1 領域:交易員和交易

    (1) 找出2011年發生的所有交易,並按交易額排序(從低到高)。
    (2) 交易員都在哪些不同的城市工作過?
    (3) 查找所有來自於劍橋的交易員,並按姓名排序。
    (4) 返回所有交易員的姓名字符串,按字母順序排序。
    (5) 有沒有交易員是在米蘭工作的?
    (6) 打印生活在劍橋的交易員的所有交易額。
    (7) 所有交易中,最高的交易額是多少?
    (8) 找到交易額最小的交易

  5.5.2 解答
5.6 數值流

  reduce方法計算流中元素的總和,暗含的裝箱成本
  5.6.1 原始類型流特化

    1. 映射到數值流

    2. 轉換回對象流

    3. 默認值OptionalInt

  5.6.2 數值范圍

    range是不包含結束值的,而rangeClosed則包含結束值

  5.6.3 數值流應用:勾股數

    1. 勾股數

    2. 表示三元數

    3. 篩選成立的組合

filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)

    4. 生成三元組

stream.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
        .map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});

    5. 生成b值

IntStream.rangeClosed(1, 100)
        .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
        .mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});

    6. 生成值

Stream<int[]> pythagoreanTriples =
        IntStream.rangeClosed(1, 100).boxed()
            .flatMap(a ->
                IntStream.rangeClosed(a, 100)
                    .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
                    .mapToObj(b ->
                    new int[]{a, b, (int)Math.sqrt(a * a + b * b)})
                    );

    7. 運行代碼

pythagoreanTriples.limit(5)
        .forEach(t ->
        System.out.println(t[0] + ", " + t[1] + ", " + t[2]));

    8. 你還能做得更好嗎?

Stream<double[]> pythagoreanTriples2 =
        IntStream.rangeClosed(1, 100).boxed()
            .flatMap(a ->
                IntStream.rangeClosed(a, 100)
                    .mapToObj(
                        b -> new double[]{a, b, Math.sqrt(a*a + b*b)})
                    .filter(t -> t[2] % 1 == 0));

5.7 構建流
  5.7.1 由值創建流

        Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
        stream.map(String::toUpperCase).forEach(System.out::println);

  5.7.2 由數組創建流

        int[] numbers = {2, 3, 5, 7, 11, 13};
        int sum = Arrays.stream(numbers).sum();

  5.7.3 由文件生成流

  5.7.4 由函數生成流:創建無限流

    1. 迭代

        Stream.iterate(0, n -> n + 2)
                .limit(10)
                .forEach(System.out::println);

    2. 生成

      在並行代碼中使用有狀態的供應源是不安全的,下面的代碼僅僅是為了內容完整,應盡量避免使用   

        IntSupplier fib = new IntSupplier(){
            private int previous = 0;
            private int current = 1;
            public int getAsInt(){
                int oldPrevious = this.previous;
                int nextValue = this.previous + this.current;
                this.previous = this.current;
                this.current = nextValue;
                return oldPrevious;
            }
        };
        IntStream.generate(fib).limit(10).forEach(System.out::println);

  5.8 小結

第 6 章 用流收集數據

6.1 收集器簡介

  函數式編程

        Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream().collect(groupingBy(Transaction::getCurrency));

  6.1.1 收集器用作高級歸約

    按貨幣對交易分組的歸約過程

  6.1.2 預定義收集器
6.2 歸約和匯總
  6.2.1 查找流中的最大值和最小值 

        Optional<Dish> mostCalorieDish =
                menu.stream()
                        .collect(maxBy(dishCaloriesComparator));

  6.2.2 匯總

    summingInt收集器的累積過程

int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

    summarizingInt工廠方法返回的收集器,通過一次summarizing操作你可以就數出菜單中元素的個數,並得到菜餚熱量總和、平均值、最大值和最小值

        IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));

  6.2.3 連接字符串

String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));

  6.2.4 廣義的歸約匯總

    1. 收集框架的靈活性:以不同的方法執行同樣的操作

      計算菜單總熱量的歸約過程

        int totalCalories = menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();    }

    2. 根據情況選擇最佳解決方案

6.3 分組

  在分組過程中對流中的項目進行分類

  6.3.1 多級分組

    

        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;
                                })
                        )
                );

    n層嵌套映射和n維分類表之間的等價關系

  6.3.2 按子組收集數據

    1. 把收集器的結果轉換為另一種類型

 

    2. 與groupingBy聯合使用的其他收集器的例子

      嵌套收集器來獲得多重效果

        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; },
                                toCollection(HashSet::new) )));

6.4 分區
  6.4.1 分區的優勢

  6.4.2 將數字按質數和非質數分區 

    Collectors類的靜態工廠方法

6.5 收集器接口

  Collector接口

  6.5.1 理解 Collector 接口聲明的方法

    1. 建立新的結果容器:supplier方法

        public Supplier<List<T>> supplier() {
            return ArrayList::new;
        }

    2. 將元素添加到結果容器:accumulator方法

        public BiConsumer<List<T>, T> accumulator() {
            return List::add;
        }

    3. 對結果容器應用最終轉換: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; }
        }

      使用combiner方法來並行化歸約過程  

    5. characteristics方法

      UNORDERED——歸約結果不受流中項目的遍歷和累積順序的影響。
      CONCURRENT——accumulator函數可以從多個線程同時調用,且該收集器可以並行歸約流。如果收集器沒有標為UNORDERED,那它僅在用於無序數據源時才可以並行歸約。
      IDENTITY_FINISH——這表明完成器方法返回的函數是一個恆等函數,可以跳過。這種情況下,累加器對象將會直接用作歸約過程的最終結果。這也意味着,將累加器A不加檢查地轉換為結果R是安全的。

  6.5.2 全部融合到一起

6.6 開發你自己的收集器以獲得更好的性能 
  6.6.1 僅用質數做除數

    1. 第一步:定義Collector類的簽名

    2. 第二步:實現歸約過程

    3. 第三步:讓收集器並行工作(如果可能)

        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;
            };
        }

    4. 第四步:finisher方法和收集器的characteristics方法

  6.6.2 比較收集器的性能
6.7 小結

第 7 章 並行數據處理與性能

7.1 並行流
  7.1.1 將順序流轉換為並行流

    並行歸納操作

  7.1.2 測量流性能

    使用更有針對性的方法

        public static long parallelRangedSum(long n) {
            return LongStream.rangeClosed(1, n)
                    .parallel()
                    .reduce(0L, Long::sum);
        }

  7.1.3 正確使用並行流

    forEach中調用的方法有副作用,它會改變多個線程共享的對象的可變狀態

  7.1.4 高效使用並行流

    用適當的基准來檢查其性能。

    自動裝箱和拆箱操作會大大降低性能。Java 8中有原始類型流(IntStream、LongStream、DoubleStream)來避免這種操作

    有些操作本身在並行流上的性能就比順序流差。特別是limit和findFirst等依賴於元素順序的操作,它們在並行流上執行的代價非常大

    要考慮流背后的數據結構是否易於分解。例如,ArrayList的拆分效率比LinkedList高得多,因為前者用不着遍歷就可以平均拆分,而后者則必須遍歷

    流的數據源和可分解性

7.2 分支/合並框架
  7.2.1 使用 RecursiveTask

    分支/合並過程

    用分支/合並框架執行並行求和

    分支/合並算法

  7.2.2 使用分支/合並框架的最佳做法

    對一個任務調用join方法會阻塞調用方,直到該任務做出結果

    不應該在RecursiveTask內部使用ForkJoinPool的invoke方法

    對子任務調用fork方法可以把它排進ForkJoinPool

    和並行流一樣,你不應理所當然地認為在多核處理器上使用分支/合並框架就比順序計算快

  7.2.3 工作竊取

    分支/合並框架工程用一種稱為工作竊取(work stealing)的技術來解決這個問題

    分支/合並框架使用的工作竊取算法

7.3 Spliterator
  7.3.1 拆分過程

    遞歸拆分過程

    Spliterator的特性

  7.3.2 實現你自己的 Spliterator

    1. 以函數式風格重寫單詞計數器

        Stream<Character> stream = IntStream.range(0, SENTENCE.length())
                .mapToObj(SENTENCE::charAt);

      用來在遍歷Character流時計數的類

    2. 讓WordCounter並行工作

      WordCounterSpliterator

    3. 運用WordCounterSpliterator

Spliterator<Character> spliterator = new WordCounterSpliterator(SENTENCE); 
Stream<Character> stream = StreamSupport.stream(spliterator, true);

7.4 小結

第 8 章 重構、測試和調試

8.1 為改善可讀性和靈活性重構代碼
  8.1.1 改善代碼的可讀性
  8.1.2 從匿名類到 Lambda 表達式的轉換 
  8.1.3 從 Lambda 表達式到方法引用的轉換
  8.1.4 從命令式的數據處理切換到Stream

  8.1.5 增加代碼的靈活性

    1 采用函數接口

    2. 有條件的延遲執行

      你需要頻繁地從客戶端代碼去查詢一個對象的狀態(比如前文例子中的日志器的狀態),只是為了傳遞參數、調用該對象的一個方法(比如輸出一條日志),那么可以考慮實現一個新的方法,以Lambda或者方法表達式作為參數

    3. 環繞執行

      擁有同樣的准備和清理階段

8.2 使用 Lambda 重構面向對象的設計模式
  8.2.1 策略模式

    圖示

 

    使用Lambda表達式

  8.2.2 模板方法

  8.2.3 觀察者模式

    觀察者模式是一種比較常見的方案,某些事件發生時(比如狀態轉變),如果一個對象(通常我們稱之為主題)需要自動地通知其他多個對象(稱為觀察者),就會采用該方案

        f.registerObserver((String tweet) -> {
            if(tweet != null && tweet.contains("money")){
                System.out.println("Breaking news in NY! " + tweet);
            }
        });
        f.registerObserver((String tweet) -> {
            if(tweet != null && tweet.contains("queen")){
                System.out.println("Yet another news in London... " + tweet);
            }
        });

  8.2.4 責任鏈模式

    責任鏈模式是一種創建處理對象序列(比如操作序列)的通用方案

  8.2.5 工廠模式

    使用工廠模式,你無需向客戶暴露實例化的邏輯就能完成對象的創建
8.3 測試 Lambda 表達式
  8.3.1 測試可見 Lambda 函數的行為 
  8.3.2 測試使用 Lambda 的方法的行為
  8.3.3 將復雜的 Lambda 表達式分到不同的方法
  8.3.4 高階函數的測試
8.4 調試
  8.4.1 查看棧跟蹤
  8.4.2 使用日志調試

8.5 小結

第 9 章 默認方法

9.1 不斷演進的 AP
  9.1.1 初始版本的 API

    向接口添加方法

  9.1.2 第二版 API
9.2 概述默認方法
9.3 默認方法的使用模式
  9.3.1 可選方法
  9.3.2 行為的多繼承

    1. 類型的多繼承

    2. 利用正交方法的精簡接口

    3. 組合接口

9.4 解決沖突的規則
  9.4.1 解決問題的三條規則

    類中的方法優先級最高。類或父類中聲明的方法的優先級高於任何聲明為默認方法的優先級。
    如果無法依據第一條進行判斷,那么子接口的優先級更高:函數簽名相同時,優先選擇擁有最具體實現的默認方法的接口,即如果B繼承了A,那么B就比A更加具體。
    最后,如果還是無法判斷,繼承了多個接口的類必須通過顯式覆蓋和調用期望的方法

  9.4.2 選擇提供了最具體實現的默認方法的接口
  9.4.3 沖突及如何顯式地消除歧義
  9.4.4 菱形繼承問題
9.5 小結

第 10 章 用 Optional 取代 null

10.1 如何為缺失的值建模
  10.1.1 采用防御式檢查減少 NullPointerException

    null-安全的第一種嘗試:深層質疑

        public String getCarInsuranceName(Person person) {
            if (person != null) {
                Car car = person.getCar();
                if (car != null) {
                    Insurance insurance = car.getInsurance();
                    if (insurance != null) {
                        return insurance.getName();
                    }
                }
            }
            return "Unknown";
        }

    null-安全的第二種嘗試:過多的退出語句

        public String getCarInsuranceName(Person person) {
            if (person == null) {
                return "Unknown";
            }
            Car car = person.getCar();
            if (car == null) {
                return "Unknown";
            }
            Insurance insurance = car.getInsurance();
            if (insurance == null) {
                return "Unknown";
            }
            return insurance.getName();
        }

  10.1.2 null 帶來的種種問題
  10.1.3 其他語言中 null 的替代品
10.2 Optional 類入門

  使用Optional定義的Car類

  使用Optional重新定義Person/Car/Insurance的數據模型

10.3 應用 Optional 的幾種模式
  10.3.1 創建 Optional 對象

    1. 聲明一個空的Optional

Optional<Car> optCar = Optional.empty();

    2. 依據一個非空值創建Optional

Optional<Car> optCar = Optional.of(car);

    3. 可接受null的Optional

Optional<Car> optCar = Optional.ofNullable(car);

  10.3.2 使用 map 從 Optional 對象中提取和轉換值

    Stream和Optional的map方法對比

  10.3.3 使用 flatMap 鏈接Optional 對象

    1. 使用Optional獲取car的保險公司名稱

        public String getCarInsuranceName(Optional<Person> person) {
            return person.flatMap(Person::getCar)
                    .flatMap(Car::getInsurance)
                    .map(Insurance::getName)
                    .orElse("Unknown");
        }

    2. 使用Optional解引用串接的Person/Car/Insurance對象

  10.3.4 默認行為及解引用Optional 對象

    get()是這些方法中最簡單但又最不安全的方法。如果變量存在,它直接返回封裝的變量值,否則就拋出一個NoSuchElementException異常。
    orElse(T other)是我們在代碼清單10-5中使用的方法,正如之前提到的,它允許你在Optional對象不包含值時提供一個默認值。
    orElseGet(Supplier<? extends T> other)是orElse方法的延遲調用版,Supplier方法只有在Optional對象不含值時才執行調用。
    orElseThrow(Supplier<? extends X> exceptionSupplier)和get方法非常類似,它們遭遇Optional對象為空時都會拋出一個異常,但是使用orElseThrow你可以定制希望拋出的異常類型。
    ifPresent(Consumer<? super T>)讓你能在變量值存在時執行一個作為參數傳入的方法,否則就不進行任何操作

  10.3.5 兩個 Optional 對象的組合
  10.3.6 使用 filter 剔除特定的值

        Optional<Insurance> optInsurance = ...;
        optInsurance.filter(insurance ->
                "CambridgeInsurance".equals(insurance.getName()))
                .ifPresent(x -> System.out.println("ok"));

    Optional類的方法

10.4 使用 Optional 的實戰示例
  10.4.1 用 Optional 封裝可能為null 的值

Optional<Object> value = Optional.ofNullable(map.get("key"));

  10.4.2 異常與 Optional 的對比 

    基礎類型的Optional對象,以及為什么應該避免使用它們
  10.4.3 把所有內容整合起來

    以命令式編程的方式從屬性中讀取duration值

    使用Optional從屬性中讀取duration

    public int readDuration(Properties props, String name) {
        return Optional.ofNullable(props.getProperty(name))
                .flatMap(OptionalUtility::stringToInt)
                .filter(i -> i > 0)
                .orElse(0);
    }

10.5 小結

第 11 章 CompletableFuture:組合式異步編程

11.1 Future 接口 

  典型的“混聚”式應用

  並發和並行

    使用Future以異步的方式執行一個耗時的操作

  11.1.1 Future 接口的局限性

    很難表述Future結果之間的依賴性

  11.1.2 使用 CompletableFuture 構建異步應用

11.2 實現異步 API
  11.2.1 將同步方法轉換為異步方法

    同步

    public double getPrice(String product) {
        return calculatePrice(product);
    }
    private double calculatePrice(String product) {
        delay();
        return random.nextDouble() * product.charAt(0) + product.charAt(1);
    }

    getPriceAsync方法的實現

  11.2.2 錯誤處理

    拋出CompletableFuture內的異常

    使用工廠方法supplyAsync創建CompletableFuture

    public Future<Double> getPriceAsync(String product) {
        return CompletableFuture.supplyAsync(() -> calculatePrice(product));
    }

11.3 讓你的代碼免受阻塞之苦
  11.3.1 使用並行流對請求進行並行操作

    public List<String> findPrices(String product) {
        return shops.parallelStream()
                .map(shop -> String.format("%s price is %.2f",
                        shop.getName(), shop.getPrice(product)))
                .collect(toList());
    }

  11.3.2 使用 CompletableFuture 發起異步請求

    為什么Stream的延遲特性會引起順序執行,以及如何避免

  11.3.3 尋找更好的方案
  11.3.4 使用定制的執行器

    如果你進行的是計算密集型的操作,並且沒有I/O,那么推薦使用Stream接口

    如果你並行的工作單元還涉及等待I/O的操作(包括網絡連接等待),那么使用CompletableFuture靈活性更好

11.4 對多個異步任務進行流水線操作
  11.4.1 實現折扣服務

  11.4.2 使用 Discount 服務

  11.4.3 構造同步和異步操作

    構造同步操作和異步任務

  11.4.4 將兩個 CompletableFuture 對象整合起來,無論它們是否存在依賴

    合並兩個相互獨立的異步任務

  11.4.5 對 Future 和 CompletableFuture 的回顧 .
11.5 響應 CompletableFuture 的completion 事件
  11.5.1 對最佳價格查詢器應用的優化

    重構findPrices方法返回一個由Future構成的流

    public Stream<CompletableFuture<String>> findPricesStream(String product) {
        return shops.stream()
                .map(shop -> CompletableFuture.supplyAsync(
                        () -> shop.getPrice(product), executor))
                .map(future -> future.thenApply(Quote::parse))
                .map(future -> future.thenCompose(quote ->
                        CompletableFuture.supplyAsync(
                                () -> Discount.applyDiscount(quote), executor)));
    }

    響應CompletableFuture的completion事件

    CompletableFuture[] futures = findPricesStream("myPhone")
            .map(f -> f.thenAccept(System.out::println))
            .toArray(size -> new CompletableFuture[size]); 
    CompletableFuture.allOf(futures).join();

  11.5.2 付諸實踐
11.6 小結

第 12 章 新的日期和時間 API

12.1 LocalDate、LocalTime、Instant、Duration 以及 Period
  12.1.1 使用 LocalDate 和LocalTime

    創建一個LocalDate對象並讀取其值

    創建LocalTime並讀取其值

  12.1.2 合並日期和時間

    直接創建LocalDateTime對象,或者通過合並日期和時間的方式創建

        // 2014-03-18T13:45:20 
        LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);
        LocalDateTime dt2 = LocalDateTime.of(date, time);
        LocalDateTime dt3 = date.atTime(13, 45, 20);
        LocalDateTime dt4 = date.atTime(time);
        LocalDateTime dt5 = time.atDate(date);

  12.1.3 機器的日期和時間格式
  12.1.4 定義 Duration 或Period

    創建Duration和Period對象

        Duration threeMinutes = Duration.ofMinutes(3);
        Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
        Period tenDays = Period.ofDays(10);
        Period threeWeeks = Period.ofWeeks(3);
        Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);

12.2 操縱、解析和格式化日期
  12.2.1 使用 TemporalAdjuster

    以比較直觀的方式操縱LocalDate的屬性

    以相對方式修改LocalDate對象的屬性

    表示時間點的日期時間類的通用方法

    使用預定義的TemporalAdjuster

    TemporalAdjuster類中的工廠方法

    NextWorkingDay類

  12.2.2 打印輸出及解析日期時間對象

    按照某個模式創建DateTimeFormatter

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
        LocalDate date1 = LocalDate.of(2014, 3, 18);
        String formattedDate = date1.format(formatter);
        LocalDate date2 = LocalDate.parse(formattedDate, formatter);

    創建一個本地化的DateTimeFormatter

        DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
        LocalDate date1 = LocalDate.of(2014, 3, 18);
        String formattedDate = date.format(italianFormatter); // 18. marzo 2014 
        LocalDate date2 = LocalDate.parse(formattedDate, italianFormatter);

12.3 處理不同的時區和歷法
  12.3.1 利用和 UTC/格林尼治時間的固定偏差計算時區
  12.3.2 使用別的日歷系統

第 13 章 函數式的思考

13.1 實現和維護系統
  13.1.1 共享的可變數據

  13.1.2 聲明式編程
  13.1.3 為什么要采用函數式編程
13.2 什么是函數式編程
  13.2.1 函數式 Java 編程
  13.2.2 引用透明性
  13.2.3 面向對象的編程和函數式編程的對比
  13.2.4 函數式編程實戰
13.3 遞歸和迭代

  基於“尾遞”的階乘

    static long factorialTailRecursive(long n) {
        return factorialHelper(1, n);
    } 
    static long factorialHelper(long acc, long n) {
        return n == 1 ? acc : factorialHelper(acc * n, n-1);
    }

第 14 章 函數式編程的技巧

14.1 無處不在的函數
  14.1.1 高階函數 
  14.1.2 科里化

    科里化是一種將具備2個參數(比如,x和y)的函數f轉化為使用一個參數的函數g,並且這個函數的返回值也是一個函數,它會作為新函數的一個參數。后者的返回值和初始函數的返回值相同,即f(x,y) = (g(x))(y)

14.2 持久化數據結構
  14.2.1 破壞式更新和函數式更新的比較
  14.2.2 另一個使用 Tree 的例子
  14.2.3 采用函數式的方法
14.3 Stream 的延遲計算
  14.3.1 自定義的 Stream
  14.3.2 創建你自己的延遲列表
14.4 模式匹配
  14.4.1 訪問者設計模式
  14.4.2 用模式匹配力挽狂瀾
14.5 雜項
  14.5.1 緩存或記憶表
  14.5.2 “返回同樣的對象”意味着什么
  14.5.3 結合器

第 15 章 面向對象和函數式編程的混合:Java 8 和 Scala 的比較

15.1 Scala 簡介
  15.1.1 你好,啤酒
  15.1.2 基礎數據結構:List、Set、Map、Tuple、Stream 以及 Option

15.2 函數
  15.2.1 Scala 中的一等函數
  15.2.2 匿名函數和閉包
  15.2.3 科里化
15.3 類和 trait
  15.3.1 更加簡潔的 Scala 類
  15.3.2 Scala 的 trait 與 Java 8 的接口對比

第 16 章 結論以及 Java 的未來

16.1 回顧 Java 8 的語言特性
  16.1.1 行為參數化(Lambda 以及方法引用
  16.1.2 流
  16.1.3 CompletableFuture
  16.1.4 Optional
  16.1.5 默認方法
16.2 Java 的未來
  16.2.1 集合
  16.2.2 類型系統的改進
  16.2.3 模式匹配
  16.2.4 更加豐富的泛型形式
  16.2.5 對不變性的更深層支持
  16.2.6 值類型
附錄 A 其他語言特性的更新

  A.1 注解

    A.1.1 重復注解

    A.1.2 類型注解

  A.2 通用目標類型推斷
附錄 B 類庫的更新

  B.1 集合

    B.1.1 其他新增的方法

    B.1.2 Collections 類

    B.1.3 Comparator

  B.2 並發

    B.2.1 原子操作

  Adder和Accumulator

    B.2.2 ConcurrentHashMap

  B.3 Arrays

    B.3.1 使用 parallelSort

      parallelSort方法會以並發的方式對指定的數組進行排序,你可以使用自然順序,也可以為數組對象定義特別的Comparator

    B.3.2 使用 setAll 和 parallelSetAll

    B.3.3 使用 parallelPrefix

  B.4 Number 和 Math

    B.4.1 Number

    B.4.2 Math

  B.5 Files

  B.6 Reflection

  B.7 String

附錄 C 如何以並發方式在同一個流上執行多種操作 

  C.1 復制流

    StreamForker詳解

    C.1.1 使用 ForkingStreamConsumer 實現 Results 接口

    C.1.2 開發 ForkingStreamConsumer 和 BlockingQueueSpliterator

    C.1.3 將 StreamForker 運用於實戰

        Stream<Dish> menuStream = menu.stream();
        StreamForker.Results results = new StreamForker<Dish>(menuStream)
                .fork("shortMenu", s -> s.map(Dish::getName)
                        .collect(joining(", ")))
                .fork("totalCalories", s -> s.mapToInt(Dish::getCalories).sum())
                .fork("mostCaloricDish", s -> s.collect(reducing(
                        (d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2))
                        .get())
                .fork("dishesByType", s -> s.collect(groupingBy(Dish::getType)))
                .getResults();
        String shortMenu = results.get("shortMenu");
        int totalCalories = results.get("totalCalories");
        Dish mostCaloricDish = results.get("mostCaloricDish");
        Map<Dish.Type, List<Dish>> dishesByType = results.get("dishesByType");
        System.out.println("Short menu: "+shortMenu);
        System.out.println("Total calories: "+totalCalories);
        System.out.println("Most caloric dish: "+mostCaloricDish);
        System.out.println("Dishes by type: "+dishesByType);

  C.2 性能的考量

附錄 D Lambda 表達式和 JVM 字節碼

  D.1 匿名類

    編譯器會為每個匿名類生成一個新的.class文件

  D.2 生成字節碼

  D.3 用 InvokeDynamic 力挽狂瀾

  D.4 代碼生成策略


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM