Java8 實戰總結

1、基本概念

兩個新特性:java

一、函數式編程簡化代碼複雜度(引入了Lambda表達式)
二、更高效的利用多核CPU

一、基本概念:

一、Lambda基本語法
(parameters) -> expression
對應:參數->表達式
 或(請注意語句的花括號)
(parameters) -> { statements; }
對應:參數->語句

根據上述語法規則,如下哪一個不是有效的Lambda表達式?數據庫

(1) () -> {}
(2) () -> "Raoul"
(3) () -> {return "Mario";}
(4) (Integer i) -> return "Alan" + i;
(5) (String s) -> {"IronMan";}

答案:只有4和5是無效的Lambda。express

(1) 這個Lambda沒有參數,並返回void。它相似於主體爲空的方法:public void run() {}
(2) 這個Lambda沒有參數,並返回String做爲表達式。
(3) 這個Lambda沒有參數,並返回String(利用顯式返回語句)。
(4) return是一個控制流語句。要使此 Lambda有效,須要使花括號,以下所示:
(Integer i) -> {return "Alan" + i;}。
(5)"Iron Man"是一個表達式,不是一個語句。要使此 Lambda有效,你能夠去除花括號
和分號,以下所示:(String s) -> "Iron Man"。或者若是你喜歡,可使用顯式返回語
句,以下所示:(String s)->{return "IronMan";}編程

二、函數式接口

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

 
 下列哪些式函數式接口:

(1)public interface Adder{
   int add(int a, int b);
   }
(2)public interface SmartAdder extends Adder{
  int add(double a, double b);
  }
(3)public interface Nothing{
 
  }
  

只有(1)是函數式接口,按照定義其餘都不是。

三、Java 8中的抽象類和抽象接口

(1)一個類只能繼承一個抽象類,可是一個類能夠實現多個接口。
(2)一個抽象類能夠經過實例變量(字段)保存一個通用狀態,而接口是不能有實例變量的。設計模式

四、多個接口都實現一樣的默認方法,子類繼承時的規則:

(1)  類中的方法優先級最高。類或父類中聲明的方法的優先級高於任何聲明爲默認方法的優先級。
(2)  若是沒法依據第一條進行判斷,那麼子接口的優先級更高:函數簽名相同時,
優先選擇擁有最具體實現的默認方法的接口,即若是B 繼承了A ,那麼B 就比A 更加具體。
(3)  最後,若是仍是沒法判斷,繼承了多個接口的類必須經過顯式覆蓋和調用指望的方法,
顯式地選擇使用哪個默認方法的實現。

五、教你一步步完成普通方法到Lambda方法的改造

目標:對於一個`Apple`列表(`inventory`)按照重量進行排序
最終實現:`inventory.sort(comparing(Apple::getWeight))`

實現分析:
Java 8API已經爲你提供了一個List可用的sort方法,你不用本身去實現它。
那麼最困難的部分已經搞定了!可是,如何把排序策略傳遞給sort方法呢?你看,sort方法的簽名是這樣的:數組

void sort(Comparator<? super E> c)數據結構

它須要一個Comparator對象來比較兩個Apple!這就是在 Java中傳遞策略的方式:它們必須包裹在一個對象裏。
咱們說sort的行爲被參數化了:傳遞給它的排序策略不一樣,其行爲也會不一樣。多線程

step1:
public class AppleComparator implements Comparator<Apple> {
    
    public int compare(Apple a1, Apple a2){
      return a1.getWeight().compareTo(a2.getWeight());
    }
  
 }
 inventory.sort(new AppleComparator());
step2:
你在前面看到了,你可使用匿名類來改進解決方案,
   而不是實現一個`Comparator`卻只實例化一次:
inventory.sort(new Comparator<Apple>() {
   public int compare(Apple a1, Apple a2){
    return a1.getWeight().compareTo(a2.getWeight());
  }
});
step3:
使用Lambda 表達式

你的解決方案仍然挺囉嗦的。Java 8引入了Lambda表達式,
它提供了一種輕量級語法來實現相同的目標:傳遞代碼。

你看到了,在須要函數式接口的地方可使用Lambda表達式。
咱們回顧一下:函數式接口就是僅僅定義一個抽象方法的接口。

抽象方法的簽名(稱爲函數描述符)描述了Lambda表達式的簽名。
在這個例子裏,Comparator表明了函數描述符(T, T) -> int。

由於你用的是蘋果,因此它具體表明的就是(Apple, Apple) -> int。

改進後的新解決方案看上去就是這樣的了:
inventory.sort((Apple a1, Apple a2)->a1.getWeight().compareTo(a2.getWeight()));


 咱們前面解釋過了,Java 編譯器能夠根據Lambda 出現的上下文來推斷
 Lambda 表達式參數的類型 。那麼你的解決方案就能夠重寫成這樣:

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


 你的代碼還能變得更易讀一點嗎?Comparator 具備一個叫做comparing
 的靜態輔助方法,它能夠接受一個Functio 來提取Comparable 鍵值,並
 生成一個Comparator 對象。
 
 它能夠像下面這樣用(注意你如今傳遞的Lambda 只有一個參數:Lambda 
 說明了如何從蘋果中提取須要比較的鍵值):

Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());

如今你能夠把代碼再改得緊湊一點了:


 import static java.util.Comparator.comparing;
 inventory.sort(comparing((a) -> a.getWeight()));
step4:
使用方法引用
前面解釋過,方法引用就是替代那些轉發參數的Lambda表達式的語法糖。你能夠用方法引用讓你的代碼更簡潔

假設你靜態導入了
 import java.util.Comparator.comparing;
 inventory.sort(comparing(Apple::getWeight));

2、流簡介

流是`Java API`的新成員,它容許你以聲明性方式處理數據集合(經過查詢語句來表達,而不是臨時編寫一個實現)。就如今來講,你能夠把它們當作遍歷數據集的高級迭代器。
  流能夠並行的處理集合數據,無需編寫複雜的多線程代碼。
public class Dish {
   private final String name;
   private final boolean vegetarian;
   private final int calories;
   private final Type type;

   public Dish(String name, boolean vegetarian, int calories, Type type) {
       this.name = name;
       this.vegetarian = vegetarian;
       this.calories = calories;
       this.type = type;
   }

   public String getName() {
       return name;
   }

   public boolean isVegetarian() {
       return vegetarian;
   }

   public int getCalories() {
       return calories;
   }

   public Type getType() {
       return type;
   }

   public enum Type { MEAT, FISH, OTHER }

}

目標:篩選出盤子中熱量高於400的食物的名稱,並按按照熱量高低排序dom

方法一:用普通集合方式處理
List<Dish> lowCaloricDishes = new ArrayList<>();
 //一、篩選 
 for(Dish d: menu){
    if(d.getCalories() < 400){
     lowCaloricDishes.add(d);
    }
 }
  //二、排序
  Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
  
    public int compare(Dish d1, Dish d2){
        return Integer.compare(d1.getCalories(), d2.getCalories());
    }
});

 //三、統計
 List<String> lowCaloricDishesName = new ArrayList<>();

 for(Dish d: lowCaloricDishes){
    lowCaloricDishesName.add(d.getName());
 }
方法二:用流處理
List<String> lowCaloricDishesName = menu.stream()
         .filter(d->d.getCalories()<400)// 篩選
         .sorted(comparing(Dish::getCalories))//排序
         .map(d->d.getName())
         .collect(Collectors.toList());//統計
流的特色:
 元素序列——就像集合同樣,流也提供了一個接口,能夠訪問特定元素類型的一組有序值。由於集合是數據結構,因此它的主要目的是以特定的時間/空間複雜度存儲和訪問元
素(如`ArrayList` 與 `LinkedList`)。但流的目的在於表達計算,好比 `filter`、`sorted`和`map`。集合講的是數據,流講的是計算。
 源——流會使用一個提供數據的源,如集合、數組或輸入/輸出資源。 請注意,從有序集合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。
 數據處理操做——流的數據處理功能支持相似於數據庫的操做,如`filter`、`map`、`reduce`、`find`、`match`、`sort`等。流操做能夠順序執行,也可並行執行。
 流水線——不少流操做自己會返回一個流,這樣多個操做就能夠連接起來,造成一個大的流水線。流水線的操做能夠看做對數據源進行數據庫式查詢。
 內部迭代——與使用迭代器顯式迭代的集合不一樣,流的迭代操做是在背後進行的。

流與集合的區別:
 一、集合是一個內存中的數據結構,它包含數據結構中目前全部的值—— 集合中的每一個元素都得先算出來才能添加到集合中。
 二、流則是在概念上固定的數據結構(你不能添加或刪除元素),其元素則是按需計算的。



流的操做:
一、中間操做:中間操做會返回另外一個流,好比`filter、map、sort、distinct`等操做
二、終端操做: 終端操做會從流的流水線生成結果,好比`forEach、count、collect`

3、使用流

經常使用流的API介紹

一、篩選流異步

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);

二、截斷流

List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());

三、跳過元素

List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());

四、映射 對流中每個元素應用函數

List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());
List<Integer> dishNameLengths = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(toList());

五、流的扁平化

例如, 給定單詞列表["Hello","World"] ,你想要返回列表["H","e","l", "o","W","r","d"]

方式一:這個並不能返回正確的數據,解釋看圖5-5

words.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());

clipboard.png

方式二:使用`flatMap` 方法,把一個流中的每一個值都換成另外一個流,而後把全部的流鏈接起來成爲一個流。解釋看圖5-6
List<String> uniqueCharacters =
 words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());

clipboard.png

六、查找和匹配

Stream API經過allMatch 、anyMatch 、noneMatch 、findFirst 和findAny 
方法提供了查找和匹配的工具方法。

一、anyMatch方法能夠回答「流中是否有一個元素能匹配給定的謂詞」。

if(menu.stream().anyMatch(Dish::isVegetarian)){
   System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
anyMatch方法返回一個boolean,所以是一個終端操做。

二、allMatch方法的工做原理和anyMatch相似,但它會看看流中的元素是否都能匹配給定的謂詞。

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

三、noneMatch沒有任何元素與給定的謂詞匹配。

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

四、findAny方法將返回當前流中的任意元素。

Optional<Dish> dish =menu.stream().filter(Dish::isVegetarian).findAny();
    
  Optional簡介
    Optional<T>類(java.util.Optional)是一個容器類,
    表明一個值存在或不存在。在上面的代碼中,findAny可能
    什麼元素都沒找到。Java 8的庫設計人員引入了Optional<T>,
    這樣就不用返回衆所周知容易出問題的null了。

五、findFirst查找第一個元素

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

六、規約

解決了如何把一個流中的元素組合起來,經常使用函數reduce

數據求和實現
方式一:
int sum = 0;
for (int x : numbers) {
  sum += x;
}
方式二:
int sum = numbers.stream().reduce(0, (a, b) -> a + b);

解釋:
reduce接受兩個參數:
 一個初始值,這裏是0;
 一個BinaryOperator<T> 來將兩個元素結合起來產生一個新的值,這裏咱們用的是lambda (a, b) -> a + b 。


求最大值和最小值
int max = numbers.stream().reduce(0, Integer::max);

歸約方法的優點與並行化

相比於前面寫的逐步迭代求和,使用reduce的好處在於,這裏的迭代被內部迭代抽象掉了,
這讓內部實現得以選擇並行執行reduce操做。而迭代式求和例子要更新共享變量sum,
這不是那麼容易並行化的。
若是你加入了同步,極可能會發現線程競爭抵消了並行本應帶來的性能提高!這種計算的並
行化須要另外一種辦法:將輸入分塊,分塊求和,最後再合併起來。但這樣的話代碼看起來就
徹底不同了。



 使用流來對全部的元素並行求和時,你的代碼幾乎不用修改:stream()換成了`parallelStream()。
 int sum = numbers.parallelStream().reduce(0, Integer::sum);

七、構建流

從數值、數組、序列、文件以及函數來構建流

7.一、數值構建流

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

7.二、數組構建流

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

7.三、函數構建流 Stream API提供了兩個靜態方法來從函數生成流:

Stream.iterate和 Stream.generate。
 Stream.iterate迭代構建流:
 Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);

解釋:

iterate方法接受一個初始值(在這裏是0),
 還有一個依次應用在每一個產生的新值上的Lambda(UnaryOperator<t>類型)。
 這裏,咱們使用Lambda n -> n + 2,返回的是前一個元素加上2。

  Stream.generate生成流:

  Stream.generate(Math::random).limit(5).forEach(System.out::println);
  IntStream 的generate 方法會接受一個IntSupplier,能夠這樣來生成一個全是1 的無限流。
  IntStream ones = IntStream.generate(() -> 1);

4、流的收集器

收集器很是有用,由於用它能夠簡潔而靈活地定義collect 用來生成結果集合的標準,一個普通的收集器
收集器的主要功能:
 將流元素歸約和彙總爲一個值
 元素分組
 元素分區
 


首先導入類:import static java.util.stream.Collectors.*;

一、歸約和彙總

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

二、鏈接字符串

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

三、廣義的歸約彙總

int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));


解釋它須要三個參數。
 第一個參數是歸約操做的起始值,也是流中沒有元素時的返回值,因此很顯然對於數值和而言0是一個合適的值。
 第二個參數就是你在6.2.2節中使用的函數,將菜餚轉換成一個表示其所含熱量的int。
 第三個參數是一個BinaryOperator,將兩個項目累積成一個同類型的值。這裏它就是對兩個int求和。

四、分組和分區groupingBy

4.一、分組
Map<Dish.Type, List<Dish>> dishesByType =menu.stream().collect(groupingBy(Dish::getType));
4.二、多級分組
Map<Dish.Type,Map<CaloricLevel,List<Dish>>> groupByTypeAndCaloriesMap=menu.stream()
        .collect(
         Collectors.groupingBy(
             Dish::getType,
             Collectors.groupingBy(dish -> {
                 if(dish.getCalories()<=400){
                     return CaloricLevel.DIET;
                 }else if(dish.getCalories()<=700){
                     return CaloricLevel.NORMAL;
                 }else{
                     return CaloricLevel.FAT;
                 }
         })
 ));

五、分區是分組的特殊狀況:由一個謂詞(返回一個布爾值的函數)做爲分類函數,它稱分區函數 。

分區函數返回一個布爾值,這意味着獲得的分組Map 的鍵類型是Boolean ,因而它最多能夠分
爲兩組——true 是一組,false 是一組。
Map<Boolean, List<Dish>> partitionedMenu =menu.stream().collect(partitioningBy(Dish::isVegetarian));

5、代碼重構

Lambda表達式對面向對象的設計模式
 說明:須要執行的動做都很簡單,使用Lambda方法能很方便地消除僵化代碼。可是,
 觀察者的邏輯有可能十分複雜,它們可能還持有狀態,或定義了多個方法,在這些情
 形下,你仍是應該繼續使用類的方式。

一、使用Lambda 表達式重構代碼

//具體處理文件接口
public interface BufferReaderProcessor{
    String process(BufferedReader br) throws IOException;
}
//公共邏輯抽離:文件的打開、資源關閉
public static String processFile(String filePath,BufferReaderProcessor     processor) throws IOException {
    try(BufferedReader br=new BufferedReader(new FileReader(filePath))){
        return processor.process(br);
    }
}
//客戶端調用
String s1= processFile("data.txt",br->br.readLine());
String s2= processFile("data.txt",br->br.readLine()+br.readLine());

二、策略模式替換

//策略接口
  public interface ValidationStrategy{
    boolean execute(String  s);
  }
  //策略執行
  public class Validator{
    private  ValidationStrategy strategy;
    public  Validator(ValidationStrategy strategy){
        this.strategy=strategy;
    }
    public boolean validator(String  s){
        return strategy.execute(s);
    }
  }
  //客戶端調用
   Validator numberValidator=new Validator(s->s.matches("\\d+"));
   Validator lowerCaseValidator=new Validator(s->s.matches("[a-z]+"));
   numberValidator.validator("12345");
   lowerCaseValidator.validator("abcdefg");

6、異步編程

在Java 8中, 新增長了一個包含50個方法左右的類: CompletableFuture,
提供了很是強大的Future的擴展功能,能夠幫助咱們簡化異步編程的複雜性,
提供了函數式編程的能力,能夠經過回調的方式處理計算結果,而且提供了轉換和
組合CompletableFuture的方法。

一、基本用法

CompletableFuture實現了Future接口,所以能夠和使用Future同樣使用它。
    
    1.1complete方法使用
    
    CompletableFuture<String> completableFuture=new CompletableFuture();
    Thread thread=new Thread(()->{
        System.out.println("task doing...");
        try{
            Thread.sleep(1000);
        }catch (Exception ex){
            ex.printStackTrace();
        }
        System.out.println("task done");
        completableFuture.complete("finished");//設置任務完成標識,不然@1會一直傻等下去
    });
    thread.start();
    String result=completableFuture.get();//@1
    System.out.println("執行結果:"+result);//返回finished
    
    1.2 completeExceptionally 方法使用
    
    CompletableFuture<String> completableFuture=new CompletableFuture();
    Thread thread=new Thread(()->{
        try{
            Thread.sleep(1000);
            throw new RuntimeException("拋異常了!");
        }catch (Exception ex){
            completableFuture.completeExceptionally(ex);//異常處理
        }

    });
    thread.start();
    String result=completableFuture.get();

二、靜態方法

CompletableFuture 類自身提供了大量靜態方法,使用這些方法能更方便地進行異步編程。

2.一、allOf和anyOf的使用

allOf要求全部任務都執行完成,最後彙總執行結果;anyOf只要執行最快的線程返回,彙總結果。
  
  
   一、task1
       CompletableFuture<String> completableFuture1=CompletableFuture.supplyAsync(()->{
        try{
            Thread.sleep(1500);
        }catch (Exception ex){
            ex.printStackTrace();
        }
        System.out.println("finished 1 ");
        return "completableFutue1";
    });

  二、task2
    CompletableFuture<String> completableFuture2=CompletableFuture.supplyAsync(()->{
        try{
            Thread.sleep(1000);
        }catch (Exception ex){
            ex.printStackTrace();
        }
        System.out.println("finished 2 ");
        return "completableFuture2";
    });


    //兩個任務都要完成才能結束
    CompletableFuture<Void> allResult=CompletableFuture.allOf(completableFuture1,completableFuture2);
    allResult.join();

    //任一個任務結束就能返回
    CompletableFuture<Object> anyResult=CompletableFuture.anyOf(completableFuture1,completableFuture2);
    System.out.println("anyOf return :"+anyResult.get());

2.2 thenCompose

容許你對兩個異步操做進行流水線, 第一個操做完成時,將其結果做爲參數傳遞給第二個操做。

 CompletableFuture<String> completedFuture1=CompletableFuture.supplyAsync(()->{
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello";
    });
    
    CompletableFuture<String> completableFuture2=completedFuture1
            .thenCompose(result->CompletableFuture.supplyAsync(()->result+" world"));
    String result=completableFuture2.get();
    System.out.println("compose return:"+result);

三、ThenCombine

將兩個完  全不相干的 CompletableFuture 對象的結果整合起來,
 並且你也不但願等到第一個任務徹底結束纔開始第二項任務。
 
  CompletableFuture<String> completableFuture1=CompletableFuture.supplyAsync(()->{
        System.out.println("in completableFuture1");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("finished completableFuture1");
        return "Hello";
    });

    CompletableFuture<String> completableFuture2=completableFuture1
            .thenCombine(CompletableFuture.supplyAsync(()-> {
                System.out.println("in completableFuture2");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("finished completableFuture2");
                return " World";
            }),(result1,result2)->result1+result2);

    System.out.println(completableFuture2.get());

四、thenAccept

在每一個CompletableFuture 上註冊一個操做,
該操做會在 CompletableFuture 完成執行後調用它。
 
 CompletableFuture<String> completabledFuture1=CompletableFuture.supplyAsync(()->{
            System.out.println("in completabledFuture1");
            return "Hello";
        });
        completabledFuture1.thenAccept(result-> System.out.println("執行結果:"+result));
相關文章
相關標籤/搜索