兩個新特性: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)是函數式接口,按照定義其餘都不是。
(1)一個類只能繼承一個抽象類,可是一個類能夠實現多個接口。
(2)一個抽象類能夠經過實例變量(字段)保存一個通用狀態,而接口是不能有實例變量的。設計模式
(1) 類中的方法優先級最高。類或父類中聲明的方法的優先級高於任何聲明爲默認方法的優先級。 (2) 若是沒法依據第一條進行判斷,那麼子接口的優先級更高:函數簽名相同時, 優先選擇擁有最具體實現的默認方法的接口,即若是B 繼承了A ,那麼B 就比A 更加具體。 (3) 最後,若是仍是沒法判斷,繼承了多個接口的類必須經過顯式覆蓋和調用指望的方法, 顯式地選擇使用哪個默認方法的實現。
目標:對於一個`Apple`列表(`inventory`)按照重量進行排序 最終實現:`inventory.sort(comparing(Apple::getWeight))`
實現分析:Java 8
的 API
已經爲你提供了一個List
可用的sort
方法,你不用本身去實現它。
那麼最困難的部分已經搞定了!可是,如何把排序策略傳遞給sort
方法呢?你看,sort
方法的簽名是這樣的:數組
void sort(Comparator<? super E> c)
數據結構
它須要一個Comparator
對象來比較兩個Apple
!這就是在 Java
中傳遞策略的方式:它們必須包裹在一個對象裏。
咱們說sort
的行爲被參數化了:傳遞給它的排序策略不一樣,其行爲也會不一樣。多線程
public class AppleComparator implements Comparator<Apple> { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } } inventory.sort(new AppleComparator());
你在前面看到了,你可使用匿名類來改進解決方案, 而不是實現一個`Comparator`卻只實例化一次:
inventory.sort(new Comparator<Apple>() { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } });
使用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()));
使用方法引用 前面解釋過,方法引用就是替代那些轉發參數的Lambda表達式的語法糖。你能夠用方法引用讓你的代碼更簡潔 假設你靜態導入了 import java.util.Comparator.comparing; inventory.sort(comparing(Apple::getWeight));
流是`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`
經常使用流的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());
方式二:使用`flatMap` 方法,把一個流中的每一個值都換成另外一個流,而後把全部的流鏈接起來成爲一個流。解釋看圖5-6
List<String> uniqueCharacters = words.stream() .map(w -> w.split("")) .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList());
六、查找和匹配
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);
收集器很是有用,由於用它能夠簡潔而靈活地定義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));
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");
在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));