9102年了,你還在用for循環操做集合?

本文首發於cdream的我的博客,點擊得到更好的閱讀體驗!html

歡迎轉載,轉載請註明出處。java

前段時間公司書架多了一本《Java8 實戰》,畢竟久聞lambda的大名,因而借來一閱。這一看,簡直是驚爲天人啊,lambda,stream,java8裏簡直是滿腦子騷操做,看個人一愣一愣的。我甚至是第一次感受到了什麼叫<font color="purple">優雅</font>。python

image-20190224175258572

本文主要介紹java8中的流處理,看看java8是怎麼愉快的玩耍集合的,讓咱們來一塊兒感覺java8的魅力吧!git

<!--more-->github

我就隨便舉個例子,看看Stream有多優雅。sql

// 對蘋果按顏色彙總並績數量
Map<String, Long> appleCount = apples.stream()
    .collect(groupingBy(Apple::getColor, counting()));
// 過濾掉顏色爲黑色的蘋果,並彙總好蘋果的總金額
Double sum = apples.stream()
    .filter(i->"black".equals(i.getColor()))
    .collect(toList);

1、lambda表達式

雖然本文重點是stream,可是stream中須要傳遞lambda表達式,因此簡單介紹一下lambda表達式。lambda表達式其實就是匿名函數(anonymous function),是指一類無需定義標識符的函數或子程序。數組

java中匿名函數的表現形式,只留下入參和方法體中的內容app

// 普通函數
public void run(String s){
    System.out.print(s+"哈哈");
}
// 我不要名字啦!!!
(s)->System.out.print(s+"哈哈")

誒,過去咱們都用對象調方法的,你弄這個沒名的東西啥時候用啊?框架

java中咱們經過函數式接口來使用這種匿名函數。dom

<div class="info note">函數式接口<br>1.java中只包含一個未實現方法的接口。其中能夠有與Object中同名的方法和默認方法(java8中接口方法能夠有默認實現)。<br>2.java中函數式接口使用@FunctionalInterface進行註解。Runnable、Comparator都是函數式接口。<br>3.java.util.function包下爲咱們提供不少經常使用的函數式接口,例如Function<T,R>等。</div>

用法舉例:

// 實現Runnable中的run方法,替代匿名內部類。
Runnable r = ()->System.out.print("哈哈");
// 做爲參數傳遞。
new Thread(()-> System.out.println("haha")).start();

ArrayList<Apple> list = new ArrayList<>();
list.forEach(i-> System.out.println(i.getWeight()));


// 簡化策略模式
public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
    List<Apple>  apples = new ArrayList<>();
    for(Apple apple : inventory){
        if(p.test(apple)){
            apples.add(apple);
        }
    }
    return apples;
}
public class BigApple implement ApplePredicate{
    @Override
    public boolean test(Apple a){
        if(a.getWeight>10){
            return a
        }
    }
}
// 這是個簡單的策略模式,根據用戶的須要,建立不一樣的接口ApplePredicate實現類,調用時傳入不一樣的實現類就能夠,但問題是若是需求過多,建立的實現類也會不少,過於臃腫不方便管理。
xx.filterApple(inventory,new BigApple);
// 使用lambda表達式,不在須要建立BigApple類
xx.filterApple(inventory,i->(i.getWeight>10));

使用lambda表達式能夠簡化大量的模板代碼,而且能夠向方法直接傳遞代碼。

總之

方法出參入參來自函數式接口
//入參s,返回void
(s)->System.out.println(s);
//入參空,返回void
()->System.out.print("haha");
//入參i,返回i+1
i->i+1
//後面寫代碼塊
apple->{if(apple.getWeiht>5) return "BIG";
        else return "small";
       }

好了,很少囉嗦了,若是感興趣推薦下面的文章或《Java8實戰》的前三章。

<div class="info note">1.<a href="https://www.zhihu.com/question/20125256/answer/324121308">Lambda表達式有何用處?如何使用?</a><br>2.<a href="http://product.dangdang.com/23952002.html">java8實戰</a></div>

2、Stream

流是什麼?

Java API的新成員,它容許你使用聲明式方式處理數據集合(相似sql,經過查詢語句表達,而不是臨時編寫一個實現)。

若是有人說lambda表達式不易於理解,那還勉強能夠接受(其實過於複雜的lambda缺失很差閱讀,但一般lambda不會作太複雜的實現),但流真的很是的易懂易用。這個語法糖真的是甜死了。

<div class="warning note">注意事項:<br>1.流只能使用一次,遍歷結束就表明這個流被消耗掉了<br>2.流對集合的操做屬於內部迭代,是流幫助咱們操做,而不是外部迭代<br>3.流操做包含:數據源,中間操做鏈,終端操做三個部分。</div>

基礎流操做

List<Double> collect = list.stream()
        // 過濾掉黑色的蘋果
        .filter(i -> "black".equals(i.getColor()))
        // 讓蘋果按照重量個價格排序
        .sorted(Comparator.comparing(Apple::getWeight)
                .thenComparing(i->i.getPrice()))
        // 篩選掉重複的數據
        .distinct()
        // 只要蘋果的價格
        .map(Apple::getPrice)
        // 只留下前兩條數據
        .limit(2)
        // 以集合的形式返回
        .collect(toList());
// 循環打印列表中元素
list.forEach(i->System.out.print(i));

Apple::getPrince<=>i -> i.getPrince()能夠看作是僅涉及單一方法的語法糖,效果與lambda表達式相同,但可讀性更好。

同理

下面列表爲常見操做

中間

操做 類型 做用 函數描述 函數
filter 中間 過濾 T -> boolean Predicate<T>
sorted 中間 排序 (T,T)->int Comparator<T>
map 中間 映射 T->R Function<T,R>
limit 中間 截斷
distinct 中間 去重,根據equals方法
skip 中間 跳過前n個元素

終端

操做 類型 做用
forEach 終端 消費流中的每一個元素,使用lambda進行操做
count 終端 返回元素個數,long
collect 終端 將流歸約成一個集合,如List,Map甚至是Integer

篩選與切片

List<String> strings = Arrays.asList("Hello", "World");
List<String> collect1 = strings.stream()
    // String映射成String[]
    .map(i -> i.split(""))
    // Arrays::Stream 數據數組,返回一個流String[]->Stream<String>
    // flatMap各數組並不分別映射成一個流,而是映射成流的內容 Stream<String>->Stream
    .flatMap(Arrays::stream)
    .collect(toList());
System.out.println(collect);
----->輸出 [H, e, l, l, o, W, o, r, l, d]

歸約操做reduce

List<Integer> integers = Arrays.asList(12, 3, 45, 3, 2,-1);
// 有初始值的疊加操做
Integer reduce = integers.stream().reduce(3, (i, j) -> i + j);
Integer reduce2 = integers.stream().reduce(5, (x, y) -> x < y ? x : y);
// 無初始值的疊加操做
Optional<Integer> reduce1 = integers.stream().reduce((i, j) -> i + j);
// 無初始值的最大值
Optional<Integer> reduce4 = integers.stream().reduce(Integer::min);
// 無初始值的最大值
Optional<Integer> reduce5 = integers.stream().reduce(Integer::max);
// 求和
Optional<Integer> reduce6 = integers.stream().reduce(Integer::sum);

reduce作的事情是取兩個數進行操做,結果返回取下一個數操做,以次類推。

Optional是java8引入的新類,避免形成空指針異常,在集合爲空時,結果會包在Optional中,能夠用isPresent()方法來判斷是否爲空值。

無初始值的狀況下可能爲空,故返回Optional

中間

操做 類型 做用 函數描述 函數
flatmap 中間 使經過的流返回內容 T -> boolean Predicate<T>

終端

操做 類型 做用
anyMatch 終端 返回boolean,判斷是否有符合條件內容
noneMatch 終端 返回boolean,判斷是否無符合條件內容
allMatch 終端 返回boolean,判斷是全爲符合條件內容
findAny 終端 Optional<T>,隨機找一個元素返回
findFirst 終端 Optional<T>,返回第一個元素
reduce 終端 Optional<T> (T,T)->T 歸約操做

數值流

包裝類型的各類操做都會有拆箱操做和裝箱操做,嚴重影響性能。因此Java8爲咱們提供了原始數值流。

// 數值流求平均值
OptionalDouble average = apples.stream()
        .mapToDouble(Apple::getPrice)
        .average();
// 數值流求和
OptionalDouble average = apples.stream()
    .mapToDouble(Apple::getPrice)
    .sum();
// 數值流求最大值,沒有則返回2
double v = apples.stream()
    .mapToDouble(Apple::getPrice)
    .max().orElse(2);
// 生成隨機數
IntStream s = IntStream.rangeClosed(1,100);

下面列表爲常見數值流操做操做

中間

操做 類型 做用
rangeClosed(1,100) 中間 生成隨機數(1,100]
range(1,100) 中間 生成隨機數(1,100)
boxed() 中間 包裝成通常流
mapToObj 中間 返回爲對象流
mapToInt 中間 映射爲數值流

終端,終端操做與List<Integer>通常流相似

構建流

  1. 值建立

    Stream<String> s = Stream.of("java","python");
  2. 數組建立

    int[]  i = {2,3,4,5};
    Stream<int> = Arrays.stream(i);
  3. 由文件生成,NIO API已經更新,以便利用Stream API

    Stream<String> s = Files.lines(Paths.get("data.txt"),Charset.defaultCharset());
  4. 由函數建立流:無限流

    // 迭代
    Stream.iterate(0,n->n+2)
    	.limit(10)
        .forEach(System.out::println);
    // 生成,須要傳遞實現Supplier<T>類型的Lambda提供的新值
    Stream.generate(Math.random)
        .limit(5)
        .forEach(System.out::println);

3、總結

至此,本文講述了常見的流操做,目前排序、篩選、求和、歸約等大多數操做咱們都能實現了。與過去相比,操做集合變的簡單多了,代碼也變的更加簡練明瞭。

目前Vert.x,Spring新出的WebFlux都經過lambda表達式來簡化代碼,不久的未來,非阻塞式框架的大行其道時,lambda表達式必將變的更加劇要!

至於開篇見到的分組!!!下篇文章見~


參考資料:

  1. Java8 實戰,Raoul-Gabriel Urma,Mario Fusco,Alan Mycroft
相關文章
相關標籤/搜索