java 8 Stream流的基本使用、部分源碼(學習筆記)

Stream

CSDN地址:https://blog.csdn.net/qq_38706585/article/details/101029041
複製代碼

stream 是什麼

​ 流是Java 8的新成員,容許以聲明式來處理數據的集合(經過查詢語句來表示,並非像之前的同樣寫一個方法來處理),就如今來講,你能夠把它們當作遍歷數據集的高級迭代器。html

有下面這個集合YxUser這個pojo類記錄的用戶的基本信息,假如咱們要查找出年齡在18-30的用戶的姓名(而且按照年齡降序)在之前咱們是怎麼作的?java

在這裏插入圖片描述

java7:mysql

List<YxUser> ageList = new ArrayList<>();
        for (YxUser y : list) {
            if (y.getAge() <= 30 && y.getAge() >= 18) {
                ageList.add(y);
            }
        }
        Collections.sort(ageList, new Comparator<YxUser>() {
            @Override
            public int compare(YxUser d1, YxUser d2) {
                return Integer.compare(d1.getAge(), d2.getAge());
            }
        });
        List<String> name = new ArrayList<>();
        for (YxUser d : ageList) {
            name.add(d.getUsername());
        }
複製代碼

雖然這個方式可以實現咱們想要的東西,可是,咱們使用了好幾個變量,而且他們的做用只有一次,可是這個寫感受比較麻煩。若是別人要 id在3-9之間的的用戶的姓名呢,這些又要改動。-->java8使用流git

java8:github

List<String> collect = list.stream()
                .filter(e -> e.getAge() <= 30 && e.getAge() >= 18)
                .sorted(comparing(YxUser::getAge))
                .map(YxUser::getUsername)
                .collect(Collectors.toList());
System.out.println("collect: " + collect);
複製代碼

而且這段代碼能夠利用多核框架執行算法

List<String> collect = list.parallelStream()
             .filter(e -> e.getAge() <= 30 && e.getAge() >= 18)
             .sorted(comparing(YxUser::getAge))
             .map(YxUser::getUsername)
             .collect(Collectors.toList());
System.out.println("collect: " + collect);
複製代碼

在java8中:sql

1.代碼是以聲明式方式來寫的:想完成什麼(filter篩選年齡) 而不是像java7同樣若是來實現(利用循環和if來控制)。若是有新的需求,那麼不用從新複製代碼,只須要更改主要條件便可。設計模式

2.把幾個基礎操做連接起來,來表達複雜的數據處理流水線(在filter後面接上sorted、map和collect操做,示意圖以下),同時保持代碼清晰可讀。filter的結果被傳給了sorted方法,再傳給map方法,最後傳給collect方法api

在這裏插入圖片描述

並且新的stream api 表達能力特別強,寫出來的東西別人可能不會,可是別人可以看懂你要作什麼。bash

stream 特色

聲明性:簡潔、易讀

聲明性:簡潔、易讀

可複用:比較靈活

可並行:性能更好

流和集合的關係

在這裏插入圖片描述

流和集合的迭代器相似,只可以遍歷一次。遍歷完成後說明這個流已經被消費了。

stream:stream是隻計算當前須要的數據,在迭代過程當中,stream是放在內部迭代的,集合的迭代是放在外部。在外部迭代就會須要本身解決管理並行的問題。

外部迭代與內部迭代

外部迭代 :使用Collection接口須要用戶去作迭代(好比用for-each)。

內部迭代: 使用Streams庫,它幫你把迭代作了,還把獲得的流值存在了某個地方,你只要給出一個函數說要幹什麼就能夠了。

在上一個例子的篩選年齡18-30的兩種方法,第一個方法就是外部迭代,第二個方法就是內部迭代。

在這裏插入圖片描述

二者的區別:

外部迭代:

  1. Java 的 for-each 循環/迭代本質上是有序的,它必須按照容器指定的順序處理元素。
  2. 它限制了 JVM控制流程的可能性,而正是這種可能性使得 JVM 能夠經過從新排序、並行處理、短路、延遲處理來提供更好的性能。

內部迭代:

  1. 用戶代碼只需關注解決問題,無需關注如何解決的細節,從而變得更加清晰了。
  2. 內部迭代使得 JVM 利用短路、並行處理和亂序執行來提高性能成爲可能(JVM 是否利用了這些來優化性能取決於 JVM 實現自己,可是有了內部迭代這些至少是可能的,而對於外部迭代來講則是不可能的)

流的操做

在這裏插入圖片描述

上圖有兩個操做

  • filter、map、sorted連成一個流水線。

  • collect 觸發流水線執行而且關閉它。

鏈接起來的流稱爲 中間操做,關閉流的操做稱爲終端操做。

中間操做:它操做後會返回另一個流,讓多個操做能夠鏈接起來造成一個查詢。重要的是,除非流水線上觸發一個終端操做,不然中間操做不會執行任何處理——這是由於中間操做通常均可以合併起來,在終端操做時一次性所有處理。

終端操做:他會從流的流水線生成一個結果。結果能夠是任何東西(void\integer\list....),可是不能是流。

在這裏插入圖片描述

Stream使用

篩選

用到的例子

List<Integer> integerList =Arrays.asList(1,2,2,2,2,2,4,5,6,7,8);


 List<YxUser> list = Arrays.asList(
                new YxUser(1, "yanxgin", 12, "8237251670@qq.com", 1, true),
                new YxUser(2, "caoxindi", 16, "2737827527@qq.com", 1, false),
                new YxUser(3, "zhangsan", 18, "334899245@qq.com", 0, true),
                new YxUser(4, "lisi", 23, "774892034@qq.com", 0, false),
                new YxUser(5, "wangwu", 66, "43892475266@qq.com", 1, false),
                new YxUser(6, "zhaoliu", 46, "54654742@qq.com", 0, false),
                new YxUser(7, "liuqi", 30, "54375396@qq.com", 1, true)
        );
複製代碼

**謂詞篩選filter **

List<String> collect = list.stream()
        .filter(e -> e.getId() > 2)  //謂詞篩選
        .collect(Collectors.toList()); // 終端操做 
複製代碼

在這裏插入圖片描述

distinct顧名思義:去掉重複的。

integerList.stream()
           .filter(i->i%2==0)
           .distinct()
           .forEach(System.out::println);
//輸出: 2,4,6,8
複製代碼

在這裏插入圖片描述

limit:返回前N個數據,相似mysql的limit上。

integerList.stream()
        .sorted()
        .limit(3)
        .forEach(System.out::println);
//排序後將輸出前三個,原來的數據 :1,2,2,2,2,2,4,5,6,7,8
// 輸出:1 2 2
複製代碼

skip:過濾掉前n個元素。

integerList.stream()
        .sorted()
        .skip(2)
        .limit(2)
        .forEach(System.out::println); 
//排序後,先過濾前兩個,在輸出前兩個。實際輸出的是第3,4兩個。
// 原來的數據 :1,2,2,2,2,2,4,5,6,7,8
// 輸出:2 2
複製代碼

映射

map:通常的用法:map就是取其中的一列

List<String> collect = list.stream()
        .filter(e -> e.getId() > 2)  // 中間操做
        .map(YxUser::getUsername) // 中間操做
        .collect(Collectors.toList()); // 終端操做
// 輸出:
// collect: [zhangsan, lisi, wangwu, zhaoliu, liuqi]

複製代碼

flatMap:流的扁平化

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

List<String> strings = Arrays.asList("Hello", "World");

List<String[]> collect1 = strings.stream()
                .map(e -> e.split(""))
                .distinct()
                .collect(Collectors.toList());
System.out.println("collect1 : " + collect1);
// 輸出發現:collect1 : [[Ljava.lang.String;@799f7e29, [Ljava.lang.String;@4b85612c]
// 並非咱們想要的

複製代碼

這個方法的問題在於,傳遞給map方法的Lambda爲每一個單詞返回了一個String[](String列表) 。所以,map返回的流其實是Stream<String[]>類型的。你真正想要的是用Stream來表示一個字符流。

在這裏插入圖片描述

在這裏插入圖片描述

flatMap登場

List<String> collect2 = strings.stream()
                .map(e -> e.split(""))
                .flatMap(Arrays::stream)// 將各個流生成爲扁平化的單個流
                .distinct()
                .collect(Collectors.toList());
// 輸出:collect2 : [H, e, l, o, W, r, d]

複製代碼

在這裏插入圖片描述

在這裏插入圖片描述

匹配

anyMatch表示數據集中是否是有一個元素可以匹配給定的謂詞

allMatch 表示流中的元素是否都可以匹配給定的謂詞

noneMatch 表示流中沒有匹配改給定的謂詞

這三個方法都返回一個Boolearn,代表根據操做存在或者不存在。

boolean isHealthy = menu.stream()
						.allMatch(d -> d.getAge() < 30);

複製代碼

查找

findAny方法表示返回當前流中的任意元素

Optional<YxUser> any = list.stream()
        .filter(e -> e.getId() > 5)
        .findAny();

複製代碼

Optional:是一個容器類,表示一個值存在仍是不存在,避免findAny找不到值的時候致使null的狀況

isPresent :表示optional包含值的時候返回true,反之false

ifPresent(Consumer t) :表示存在時,執行存在的代碼塊

T get()會在值存在時返回值,不然拋出一個NoSuchElement異常。T orElse(T other)會在值存在時返回值,不然返回一個默認值

查找第一個元素 findFirst

Optional<Integer> first = integerList.stream()
                .map(x -> x * x)
                .filter(x -> x % 3 == 0)
                .findFirst();

複製代碼

歸約

reduce:首先要有一個初始值,還有第二個參數是執行規約的規則

List<Integer> integerList = Arrays.asList(1, 2, 2, 2, 2, 2, 4, 5, 6, 7, 8);
Integer reduce = integerList.stream()
        .reduce(0, (x, y) -> x + y);
Integer reduce = integerList.stream()
        .reduce(0, Integer::sum);
這兩個是同樣的 還有Integer::MAX和MIN,初始值要注意

複製代碼

在這裏插入圖片描述

Integer::MAX和MIN

max和min不須要初始值,可是接受的時候,須要用Optional來接收

Optional<Integer> reduce = integerList.stream()
     .reduce(Integer::max);
System.out.println("reduce: "+ reduce.get());

複製代碼

在這裏插入圖片描述

數值流

收集器使用groupingBy:經過用戶的用戶名進行分組以下

Map<String, List<YxUser>> collect3 = list.stream()
        .collect(groupingBy(YxUser::getUsername));

/** 輸出 collect3: { lisi=[com.yangxin.demo.model.YxUser@69663380], liuqi=[com.yangxin.demo.model.YxUser@5b37e0d2], yanxgin=[com.yangxin.demo.model.YxUser@4459eb14], caoxindi=[com.yangxin.demo.model.YxUser@5a2e4553], zhaoliu=[com.yangxin.demo.model.YxUser@28c97a5], zhangsan=[com.yangxin.demo.model.YxUser@6659c656], wangwu=[com.yangxin.demo.model.YxUser@6d5380c2]} */

/*** *多級分組 *首先按照性別分組,而後按照id分組。 */
Map<Integer, Map<String, List<YxUser>>> collect4 = list.stream()
.collect(groupingBy(YxUser::getSex, // 一級分類函數
groupingBy(e -> { // 二級函數
    if (e.getId() > 5) return "hight";
    else if (e.getId() < 4) return "small";
    else return "midle";
})));
/** 輸出 collect4: { lisi={midle=[com.yangxin.demo.model.YxUser@69663380]}, liuqi={hight=[com.yangxin.demo.model.YxUser@5b37e0d2]}, yanxgin={small=[com.yangxin.demo.model.YxUser@4459eb14]}, caoxindi={small=[com.yangxin.demo.model.YxUser@5a2e4553]}, zhaoliu={hight=[com.yangxin.demo.model.YxUser@28c97a5]}, zhangsan={small=[com.yangxin.demo.model.YxUser@6659c656]}, wangwu={midle=[com.yangxin.demo.model.YxUser@6d5380c2]}} */


// 按照子組收集數據
Map<Integer, Long> collect = list.stream()
        .collect(groupingBy(YxUser::getSex, counting()));
/** * counting 能夠換成maxBy、minBy */

複製代碼

若是是本身寫的話,會嵌套多層循環,多級分組那麼將會更難維護。

使用Collector收集數據

maxBy和minBy在collect中使用,可是參數是自定義的Comparator

Comparator<YxUser> comparator=Comparator.comparingInt(YxUser::getId);
Optional<YxUser> collect = list.stream()
        .collect(minBy(comparator));
// 使用reducing
Optional<YxUser> mostCalorieDish = list.stream().
collect(reducing( (d1, d2) -> d1.getId() < d2.getId() ? d1 : d2));

複製代碼

summingInt,在collect中計算總和。

Integer collect = list.stream().collect(summingInt(YxUser::getId));

// 若是使用reducing
int total = list.stream()
    .collect(reducing( 0, //初始值
						YxUser::getId,//轉換函數
						Integer::sum);//累積函數
//第一個參數是歸約操做的起始值,也是流中沒有元素時的返回值,因此很顯然對於數值和而言0是一個合適的值。
//第二個參數就是你在6.2.2節中使用的函數,將菜餚轉換成一個表示其所含熱量的int。
//第三個參數是一個BinaryOperator,將兩個項目累積成一個同類型的值。這裏它就是對兩個int求和

             
int sum=list.stream()
             .mapToInt(YxUSer::getId)
             .sum();

複製代碼

還有相似的函數:averagingInt計算平均值

可是還能夠經過summarizingInt能夠一次性獲得:對應的最大值、最小值、平均值、和、數量等信息,能夠經過getter獲取

在這裏插入圖片描述

joining鏈接字符串

​ joining實現字符串鏈接,是使用的StringBuilder,進行字符串拼接的

String collect1 = list.stream().map(YxUser::getUsername).collect(joining());
System.out.println("collect1:" + collect1);
// 添加分割符
String collect2 = list.stream().map(YxUser::getUsername).collect(joining(", "));
System.out.println("collect2:" + collect2);

複製代碼

輸出效果:

在這裏插入圖片描述

求和的幾種形式

list.stream().mapToInt(YxUser::getId).sum();

list.stream().map(YxUser::getId).reduce(Integer::sum).get();

list.stream().collect(reducing(0, YxUser::getId, Integer::sum));

list.stream().collect(reducing(0, YxUser::getId, (x, y) -> x + y));

複製代碼

字符串拼接的幾種形式

list.stream().map(YxUser::getUsername).collect(reducing((s1,s2)->s1+s2)).get();

list.stream().collect(reducing("",YxUser::getUsername,(s1,s2)->s1+s2));

String collect2 = list.stream().map(YxUser::getUsername)
.collect(joining(", "));
// 從性能上考慮,建議使用joining

複製代碼

partitioningBy分區函數

返回的主鍵是 boolean類型,只有true和false兩種狀況。分區其實就是分組的一種特殊狀況。

Map<Boolean, List<YxUser>> collect = list.stream()
.collect(partitioningBy(YxUser::isX));
System.out.println("collect: " + collect);

複製代碼

函數大全

在這裏插入圖片描述
在這裏插入圖片描述

收集器Collector源碼

在這裏插入圖片描述

Collector 首先有5個主要的函數:supplier、accumulator、combiner、finisher、characteristics。

supplier :調用這個函數的時候會建立一個空的累加器實例,供數據收集使用。

/** 官方的解釋 * A function that creates and returns a new mutable result container. * * @return a function which returns a new, mutable result container */
Supplier<A> supplier();

複製代碼

accumulator : accumulator函數至關因而一個累加器,進行中間結果的處理。當遍歷到流中第n個元素時,這個函數執行,時會有兩個參數:保存歸約結果的累加器(已收集了流中的前 n-1 個項目),還有第n個元素自己

/** 官方的解釋 * A function that folds a value into a mutable result container. * * @return a function which folds a value into a mutable result container */
BiConsumer<A, T> accumulator();

複製代碼

finisher : finisher函數主要是最後的工做,主要是將最後的結果進行轉換。finisher方法必須返回在累積過程的最後要調用的一個函數,以便將累加器對象轉換爲整個集合操做的最終結果。

/** * Perform the final transformation from the intermediate accumulation type * {@code A} to the final result type {@code R}. * * <p>If the characteristic {@code IDENTITY_TRANSFORM} is * set, this function may be presumed to be an identity transform with an * unchecked cast from {@code A} to {@code R}. * * @return a function which transforms the intermediate result to the final * result */
Function<A, R> finisher();

複製代碼

supplier、accumulator、finisher這三個函數就徹底夠流的順序歸約了

在這裏插入圖片描述

combiner:combiner方法會返回一個供歸約操做使用的函數,它定義了對流的各個子部分進行並行處理時,各個子部分歸約所得的累加器要如何合併。

/** * A function that accepts two partial results and merges them. The * combiner function may fold state from one argument into the other and * return that, or may return a new result container. * * @return a function which combines two partial results into a combined * result */
BinaryOperator<A> combiner();

複製代碼

supplier、accumulator、finisher這三個函數加上combiner這個函數,能夠對流進行並行歸約了,有點至關於併發環境的fork/join框架,他主要的步驟有一下幾步:

第一步:將原始的流分紅子流,知道條件不能分爲止(分得過小也很差)

第二步:全部的子流並行運行。

第三步:使用收集器combiner方法返回的函數,將全部的部分合並。

這個流程和併發的fork/join差很少,能夠參考我一篇博客:www.cnblogs.com/yangdagaoge…

characteristics :這個方法就是返回一個不可變的Characteristics,表示收集器的行爲:

​ ①:UNORDERED——歸約結果不受流中項目的遍歷和累積順序的影響

​ ②:CONCURRENT——accumulator函數能夠從多個線程同時調用,且該收集器能夠並行歸約流。

​ ③:IDENTITY_FINISH——這代表完成器方法返回的函數是一個恆等函數,能夠跳過。

可是點擊Collector的實現類的時候發現他只有一個Collectors實現類而且在Collectors中定義了一個內部類CollectorImpl,其中的實現特別簡單。以下:

/** * Simple implementation class for {@code Collector}. * * @param <T> the type of elements to be collected * @param <R> the type of the result */
    static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
        // 一系列的成員函數
        private final Supplier<A> supplier;
        private final BiConsumer<A, T> accumulator;
        private final BinaryOperator<A> combiner;
        private final Function<A, R> finisher;
        private final Set<Characteristics> characteristics;

        CollectorImpl(Supplier<A> supplier,
                      BiConsumer<A, T> accumulator,
                      BinaryOperator<A> combiner,
                      Function<A,R> finisher,
                      Set<Characteristics> characteristics) {
            this.supplier = supplier;
            this.accumulator = accumulator;
            this.combiner = combiner;
            this.finisher = finisher;
            this.characteristics = characteristics;
        }

        CollectorImpl(Supplier<A> supplier,
                      BiConsumer<A, T> accumulator,
                      BinaryOperator<A> combiner,
                      Set<Characteristics> characteristics) {
            this(supplier, accumulator, combiner, castingIdentity(), characteristics);
        }

        @Override
        public BiConsumer<A, T> accumulator() {
            return accumulator;
        }

        @Override
        public Supplier<A> supplier() {
            return supplier;
        }

        @Override
        public BinaryOperator<A> combiner() {
            return combiner;
        }

        @Override
        public Function<A, R> finisher() {
            return finisher;
        }

        @Override
        public Set<Characteristics> characteristics() {
            return characteristics;
        }
    }

複製代碼

1.toList源碼

/** * Returns a {@code Collector} that accumulates the input elements into a * new {@code List}. There are no guarantees on the type, mutability, * serializability, or thread-safety of the {@code List} returned; if more * control over the returned {@code List} is required, use {@link #toCollection(Supplier)}. * * @param <T> the type of the input elements * @return a {@code Collector} which collects all the input elements into a * {@code List}, in encounter order */
    public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new,
                        //建立一個ArrayList類型的Supplier收集器
        				List::add,// 使用list的add函數將流中的數據添加到空結果容器中
                        (left, right) -> { left.addAll(right); return left; },
                        // lambda 表達式,將右邊的list添加到左邊的list中,這就是至關於一個combiner函數
                        CH_ID);// 表示收集器的行爲參數
    }

複製代碼

使用toList

List<User> collect = list.stream().collect(Collectors.toList());

複製代碼

toSet 的源碼也是類型,不過吧Supplier 換成了 (Supplier<Set>) HashSet::new

/** * Returns a {@code Collector} that accumulates the input elements into a * new {@code Set}. There are no guarantees on the type, mutability, * serializability, or thread-safety of the {@code Set} returned; if more * control over the returned {@code Set} is required, use * {@link #toCollection(Supplier)}. * * <p>This is an {@link Collector.Characteristics#UNORDERED unordered} * Collector. * * @param <T> the type of the input elements * @return a {@code Collector} which collects all the input elements into a * {@code Set} */
 public static <T>
 Collector<T, ?, Set<T>> toSet() {
     return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
                                (left, right) -> { left.addAll(right); return left; },
                                CH_UNORDERED_ID);
 }

複製代碼

2. 字符拼接joining源碼

①.無分隔符

/** * Returns a {@code Collector} that concatenates the input elements into a * {@code String}, in encounter order. * * @return a {@code Collector} that concatenates the input elements into a * {@code String}, in encounter order * * CharSequence:這個是字符串序列接口 * joining的源碼可得,實現字符串拼接是使用 StringBuilder實現的, */
    public static Collector<CharSequence, ?, String> joining() {
        return new CollectorImpl<CharSequence, StringBuilder, String>(
                // 建立StringBuilder的結果容器
            	// StringBuilder::append:拼接函數(累加器部分)
                StringBuilder::new, StringBuilder::append,
            	// 聯合成一個值,combiner部分
                (r1, r2) -> { r1.append(r2); return r1; },
            	// 最後結果的轉換
                StringBuilder::toString, CH_NOID);
    }


複製代碼

CharSequence 這是個字符串的序列接口,String、StringBuffer、StringBuilder也是實現這個接口。它和String的區別就是,String可讀不可變,CharSequence是可讀可變

在這裏插入圖片描述

使用字符串拼接

static List<User> list = Arrays.asList(
            new User("y楊鑫", 50, 5455552),
            new User("張三", 18, 66666), 
            new User("李四", 23, 77777),
            new User("王五", 30, 99999),
            new User("趙柳", 8, 11111),
            new User("王八蛋", 99, 23233)
    );

    public static void main(String[] args) {

        String collect = list.stream().map(User::getUsername)
                .collect(joining());
        System.out.println("collect: " + collect);
    }
////////////////////////////////////////輸出/////////////////////////
collect: y楊鑫張三李四王五趙柳王八蛋

複製代碼

②.帶分割符的

/** * Returns a {@code Collector} that concatenates the input elements, * separated by the specified delimiter, in encounter order. * 返回一個帶分割符的拼接串 * @param delimiter the delimiter to be used between each element * @return A {@code Collector} which concatenates CharSequence elements, * separated by the specified delimiter, in encounter order * 將分割符傳給了joining三參數的重載函數 */
    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter){
        return joining(delimiter, "", "");
    }

	/** * Returns a {@code Collector} that concatenates the input elements, * separated by the specified delimiter, with the specified prefix and * suffix, in encounter order. * * @param delimiter the delimiter to be used between each element * @param prefix the sequence of characters to be used at the beginning * of the joined result * @param suffix the sequence of characters to be used at the end * of the joined result * @return A {@code Collector} which concatenates CharSequence elements, * separated by the specified delimiter, in encounter order * * 在這個函數中,使用了一個叫StringJoiner的類,這個是java8的封裝類,主要的功能是 * 按照 分割符delimiter,字符串開始 prefix,字符串結尾suffix,進行字符串的拼接 */
    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
                                                             CharSequence prefix,
                                                             CharSequence suffix) {
        return new CollectorImpl<>(
            	// 建立一個Supplier結果容器
                () -> new StringJoiner(delimiter, prefix, suffix),
            	// 字符串的添加至關於 accumulator累加器部分;merge是聯合將兩個數值整合成一個,至關於combiner部分
                StringJoiner::add, StringJoiner::merge,
            	// toString作最後的結果轉換
                StringJoiner::toString, CH_NOID);
    }

複製代碼

運行樣例

String collect = list.stream().map(User::getUsername)
                .collect(joining());
        System.out.println("collect: " + collect);
        
        String collect1 = list.stream().map(User::getUsername)
                .collect(joining(","));
        System.out.println("collect1: " + collect1);
        
        String collect2 = list.stream().map(User::getUsername)
                .collect(joining(",","[","]"));
        System.out.println("collect2: " + collect2);
///////////////////////輸出//////////////////////////////
collect: y楊鑫張三李四王五趙柳王八蛋
collect1: y楊鑫,張三,李四,王五,趙柳,王八蛋
collect2: [y楊鑫,張三,李四,王五,趙柳,王八蛋]


複製代碼

StringJoiner源碼:

public final class StringJoiner {
 /** * prefix:表示字符串拼接的前綴 * suffix:表示字符串拼接的結尾 * delimiter: 表示分割符 * */
 private final String prefix;
 private final String delimiter;
 private final String suffix;

 /* * StringBuilder的值。構造器從prefix開始添加元素,delimiter分割,可是沒有 * 結尾符suffix,那麼咱們每次會更容易的去拼接字符串 */
 private StringBuilder value;

 /* * 默認狀況,由prefix和suffix拼接的字符串,在返回值的時候使用toString轉換。 * 當沒有元素添加的時候,那麼這個爲空,這頗有可能被用戶去覆蓋一些其餘值,包括空串 */
 private String emptyValue;

 /** * 構造器只有delimiter分隔符,prefix和suffix將默認爲空串, */
 public StringJoiner(CharSequence delimiter) {
     this(delimiter, "", "");
 }

 /** * 三參數的構造器 */
 public StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) {
     Objects.requireNonNull(prefix, "The prefix must not be null");
     Objects.requireNonNull(delimiter, "The delimiter must not be null");
     Objects.requireNonNull(suffix, "The suffix must not be null");
     // make defensive copies of arguments
     this.prefix = prefix.toString();
     this.delimiter = delimiter.toString();
     this.suffix = suffix.toString();
     this.emptyValue = this.prefix + this.suffix;
 }

 /** * 設置空值 */
 public StringJoiner setEmptyValue(CharSequence emptyValue) {
     this.emptyValue = Objects.requireNonNull(emptyValue,
             "The empty value must not be null").toString();
     return this;
 }

 /** * * 重寫的toString,字符串將是prefix開始,suffix結尾,除非沒有添加任何元素,那 * 麼就返回空值 */
 @Override
 public String toString() {
     if (value == null) {
         return emptyValue;
     } else {
         if (suffix.equals("")) {
             return value.toString();
         } else {
             int initialLength = value.length();
             String result = value.append(suffix).toString();
             // reset value to pre-append initialLength
             value.setLength(initialLength);
             return result;
         }
     }
 }

 /** * 添加一個拼接的串 * * @param newElement The element to add * @return a reference to this {@code StringJoiner} */
 public StringJoiner add(CharSequence newElement) {
     prepareBuilder().append(newElement);
     return this;
 }

 /** * 將拼接的字串合併 */
 public StringJoiner merge(StringJoiner other) {
     Objects.requireNonNull(other);
     if (other.value != null) {
         final int length = other.value.length();
         // lock the length so that we can seize the data to be appended
         // before initiate copying to avoid interference, especially when
         // merge 'this'
         StringBuilder builder = prepareBuilder();
         builder.append(other.value, other.prefix.length(), length);
     }
     return this;
 }

 private StringBuilder prepareBuilder() {
     if (value != null) {
         value.append(delimiter);
     } else {
         value = new StringBuilder().append(prefix);
     }
     return value;
 }

 /** * 返回長度 */
 public int length() {
     // Remember that we never actually append the suffix unless we return
     // the full (present) value or some sub-string or length of it, so that
     // we can add on more if we need to.
     return (value != null ? value.length() + suffix.length() :
             emptyValue.length());
 }
}

複製代碼

測試

StringJoiner joiner = new StringJoiner(",", "[", "]");
     for (YxUser x : list) {
         joiner.add(x.getUsername());
     }
     joiner.merge(joiner);
		// 若是沒有merge將輸出:joiner: [yanxgin,12,yan34xgin,56,78,90,666]

/** 使用joiner.merge(joiner),將輸出joiner: [yanxgin,12,yan34xgin,56,78,90,666,yanxgin,12,yan34xgin,56,78,90,666],使用merge將另一個的StringJoiner合併進來,因此在這兒,他將已經又合併了一次 */
     System.out.println("joiner: " + joiner);

複製代碼

3.toCollection源碼

/** * * 返回一個{@Code Collector}收集器(輸入的元素累加到新的收集器) * {@Code Collection}集合是由工廠建立 * * @param <T> 輸入類型 * @param <C> 收集器{@code Collection}的結果類型 * @param 這個集合工廠collectionFactory將返回一個新的適當類型的收集器 * @return 按照順序將輸入的元素收集到一個{@Code Collector}而且返回 * * 函數功能:按照collectionFactory收集器的類型從新收集流中的數據, * 例如: * {@Code * LinkedList<YxUser> collect1 = list.stream().collect(Collectors.toCollection(LinkedList::new)); * // LinkedList能夠換成Collection的其餘集合類型 * } */
    public static <T, C extends Collection<T>>
    Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
        return new CollectorImpl<>(collectionFactory, Collection<T>::add,
                (r1, r2) -> { r1.addAll(r2); return r1; },
                CH_ID);
    }

複製代碼

實現列子

// toCollection的參數的意義就是建立一個類型的集合來收集他。
        list.stream().collect(Collectors.toCollection(LinkedList::new));
        list.stream().collect(Collectors.toCollection(TreeSet::new));
        ......

複製代碼

toList、toSet、toCollection區別:

​ toList:表示能夠重複、有序。

​ toSet:表示不可重複、無序。

​ toCollection:自定義實現Collection的數據結構收集。

4. mapping源碼

/** * Adapts a {@code Collector} accepting elements of type {@code U} to one * accepting elements of type {@code T} by applying a mapping function to * each input element before accumulation. * * 在輸入元素的累加前,使用mapping函數將一個接受U類型({@code U})的收集器調 * 整爲接受T類型({@code T})的收集器。**感受翻譯不太對。 * * @apiNote * {@code mapping()} mapping適用於多層次的篩選, * 例如,Person實體類集合中,計算出每一個城市的姓名、 * <pre>{@code * Map<City, Set<String>> lastNamesByCity * = people.stream().collect(groupingBy(Person::getCity, * mapping(Person::getLastName, toSet()))); * }</pre> * * @param <T> 輸入元素的類型。 * @param <U> 接受元素的類型 * @param <A> 收集器的中間累加器的類型 * @param <R> 收集器的結果類型 * @param 應用於輸入元素的函數 * @param downstream 收集器接受一個mapper的值 * @return a collector which applies the mapping function to the input * elements and provides the mapped results to the downstream collector */
    public static <T, U, A, R>
    Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
                               Collector<? super U, A, R> downstream) {
        BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
        return new CollectorImpl<>(downstream.supplier(),
                (r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)),
                downstream.combiner(), downstream.finisher(),
                downstream.characteristics());
    }

複製代碼

樣例

// 獲取郵件的性別
list.stream().collect(groupingBy(YxUser::getEmail, mapping(YxUser::getSex, toList())));

複製代碼

5.collectingAndThen

/** * * 調整一個收集器{@Code Collector} 去完成一個額外的轉換。例如, * {@link #toList()}的調節使得收集器老是產生一個不可變的list。 * <pre>{@code * List<String> people * = people.stream().collect(collectingAndThen(toList(), Collections::unmodifiableList)); * }</pre> * * @param <T> 輸入元素的類型 * @param <A> downstream collector收集器中間累加的結果類型 * @param <R> downstream collector收集器的結果 * @param <RR> 結果收集器的類型 * @param downstream a collector * @param finisher 是一個完成最終功能的函數 * @return 返回一個收尾完成的結果蒐集器 */
    public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher) {
        // 獲取收集器的行爲特性。
        Set<Collector.Characteristics> characteristics = downstream.characteristics();
        // 若是這個蒐集器 是一個恆等函數
        if (characteristics.contains(Collector.Characteristics.IDENTITY_FINISH)) {
            if (characteristics.size() == 1)
                // 長度若是爲1,那麼置空
                characteristics = Collectors.CH_NOID;
            else {
                // 使用EnumSet枚舉集合類來建立一個具備characteristics特徵的枚舉。
                characteristics = EnumSet.copyOf(characteristics);
                // 去掉恆等行爲
                characteristics.remove(Collector.Characteristics.IDENTITY_FINISH);
                //unmodifiableSet 表示是不可改的Set,與此相似的還有 unmodifiableMap
                characteristics = Collections.unmodifiableSet(characteristics);
            }
        }
        /////不是太明白爲何要作這個if的操做。
        return new CollectorImpl<>(downstream.supplier(),
                downstream.accumulator(),
                downstream.combiner(),
                downstream.finisher().andThen(finisher),
                characteristics);
    }

複製代碼

獲取性別分組中最高的Id

list.stream().collect(groupingBy(YxUser::getSex, collectingAndThen(maxBy(Comparator.comparingInt(YxUser::getId)), Optional::get)));

複製代碼

輸出

collect3: {0=YxUser{id=6, username='90', password='222', lastVisitTime=null, email='8237216470@qq.com', activation=null, createTime=null}, 1=YxUser{id=7, username='666', password='222', lastVisitTime=null, email='823721670@qq.com', activation=null, createTime=null}}

複製代碼

6.counting

/** * 返回一個流的數量,若是是空,那麼返回爲0. * * @implSpec * 這個函數的實現是依靠於 reducing實現的。 * <pre>{@code * reducing(0L, e -> 1L, Long::sum) * }</pre> * * @param <T> 輸入元素的類型 * @return a {@code Collector} 返回一個count */
    public static <T> Collector<T, ?, Long>
    counting() {
        return reducing(0L, e -> 1L, Long::sum);
    }

複製代碼

幾種count的方式

long count = list.stream().count();
        System.out.println("count: " + count);
        Long collect4 = list.stream().collect(counting());
        System.out.println("collect4: " + collect4);
        list.stream().collect(reducing(0, YxUser::getId, (x, y) -> x + y));

複製代碼

7.minBy

/** * * minBy其實實現的原理也就是 * return (a,b)->comparator.compare(a,b) < 0 ? a : b * * 可是是使用Optional<T>來接受,防止空值 * * @implSpec * This produces a result equivalent to: * <pre>{@code * reducing(BinaryOperator.minBy(comparator)) * }</pre> * * @param <T> 輸入元素的類型 * @param comparator 是一個比較器 * @return 一個最小值 */
    public static <T> Collector<T, ?, Optional<T>>
    minBy(Comparator<? super T> comparator) {
        return reducing(BinaryOperator.minBy(comparator));
    }

複製代碼

使用minBy

//本身構造的比較器,是依據YxUser中的Id來比較。
		Comparator<YxUser> comparator=Comparator.comparingInt(YxUser::getId);
        list.stream().collect(groupingBy(YxUser::getUsername, minBy(comparator)));

複製代碼

maxBy和這個差很少。

summingInt 函數,是返回最大值、最小值、平均值、count值等,使用getter方法獲取便可

IntSummaryStatistics collect5 = list.stream().collect(summarizingInt(YxUser::getId));
     collect5.getAverage();

複製代碼

因爲類型緣由,還提供了summingLong、summingDouble。

求平均值的:averagingInt、averageingLong、averagingDouble

自定義一個收集器

​ 在Collector和Collectors的源碼得知,在Collectors中,使用內部類CollectorImpl來實現的Collector,而後其餘的toList、joining等一系列的操做都是,依託於這個內部類CollectorImpl,因此咱們能夠自定義一個收集器。他的功能是:使用ArrayList來收集流中的數據:

/** * @author yangxin * @time 2019/8/6 15:02 */
public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {

    // 既然是使用ArrayList來收集流中的數據,那麼在supplier中要建立一個ArrayList結果容器,
    //在CollectorImpl中,他在這兒將建立圈交給了外部。
    @Override
    public Supplier<List<T>> supplier() {
        return ArrayList::new;
    }

    // 累加器部分,確定就是添加元素
    @Override
    public BiConsumer<List<T>, T> accumulator() {
        return List::add;
    }

    // 聯合部分,量右邊的值歸併到左邊
    @Override
    public BinaryOperator<List<T>> combiner() {
        return (list1, list2) -> {
            list1.addAll(list2);
            return list1;
        };
    }

    @Override
    public Function<List<T>, List<T>> finisher() {
        return Function.identity();
    }

    @Override
    public Set<Characteristics> characteristics() {
        // 爲收集器添加IDENTITY_FINISH和CONCURRENT標記
        return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT));
    }
}

複製代碼

使用

List<YxUser> list = Arrays.asList(
                new YxUser(1, "yanxgin", "222", "8237251670@qq.com", 1, true),
                new YxUser(2, "12", "222", "8237216670@qq.com", 1, false),
                new YxUser(3, "yan34xgin", "222", "823721670@qq.com", 0, true),
                new YxUser(4, "56", "222", "823721670@qq.com", 0, false),
                new YxUser(5, "78", "222", "82372163@qq.com", 1, false),
                new YxUser(6, "90", "222", "8237216470@qq.com", 0, false),
                new YxUser(7, "666", "222", "823721670@qq.com", 1, true)
        );

        // 自定義的收集器
        List<YxUser> collect = list.stream()
                .collect(new ToListCollector<YxUser>());
        System.out.println("collect: " + collect);

複製代碼

本身寫的ToListCollector 收集器,和toList的差很少,估計惟一的區別,就是toList有工廠類本身建立,自定義的收集器須要new。咱們能夠建立出適用本身的收集器

優化代碼

​ 在不少狀況下使用java8的特性能夠優化不少代碼,使得代碼更清晰、更易讀。

​ ①:使用Lambda表達式取代匿名類。

Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println("word");
            }
        };
        Runnable runnable1 = () -> System.out.println("hello");

複製代碼

須要注意的地方:

Lambda表達式中的this,和super的含義是不一樣的。在匿名類中,this表示自己;但在Lambda中,他表示的是包含類。然而,匿名類能夠屏蔽包含類的變量,Lambda不能屏蔽。

還有一個問題是:假若有重載函數的時候,由於Lambda表達式的參數根據上下文來肯定的,因此會出現有多個匹配項的存在。這個編譯器會幫忙解決這個問題好比:IDEA

int n=10;
     Runnable runnable2 = () -> {
         int n = 2;//會報錯
         System.out.println("n: " + n);
     };

     Runnable runnable = new Runnable() {
         @Override
         public void run() {
             int n = 2;
             System.out.println("word" + n);
         }
     };
     runnable.run();
     runnable2.run();

複製代碼

在這裏插入圖片描述

​ ②:使用方法引用重構Lambda表達式。

​ ③:使用StreamAPI重構命令式的數據處理。

使用Lambda重構面對對象的設計模式

1.策略模式

​ 策略模式就是解決一類算法的通用解決方案。一般有3部分組成:①:一個母接口(他表明某個需求)。②:實現母接口的各類策略方法。③:策略對象的客戶。

在這裏插入圖片描述

在傳統的方法中,咱們要使用策略模式這三個步驟不可少,可是有時候功能就比較簡單,好比比較學生成績大小,找出性別男女的人數、成績平均值等,那麼你須要去實現對應的策略函數,而後使用的時候以下:

// IsAllLowerCase、IsNumeric就是實現的策略函數
Validator numericValidator = new Validator(new IsNumeric()); 
boolean b1 = numericValidator.excute("aaaa");  
Validator lowerCaseValidator = new Validator(new IsAllLowerCase ());
boolean b2 = lowerCaseValidator.excute("bbbb"); 

複製代碼

使用Lambda,那麼就會省略策略實現部分:

// 函數式接口
public interface Factory {
    boolean execete(String s);
}

// 客戶端
public class FactoryImpl {

    private final Factory factory;

    public FactoryImpl(Factory factory) {
        this.factory = factory;
    }

    public boolean execute(String s){
        return factory.execete(s);
    }
}

		// 使用部分 策略模式,在簡單的策略就用實現多餘的策略方法。
		FactoryImpl factory = new FactoryImpl((String s) -> s.matches("[a-z]+"));
        System.out.println("boolean: " + factory.execute("ddd"));


複製代碼

2.模板方法

​ 模板方法就是但願使用這個算法,而且呢能夠支持對其中的修改,才能達到但願的效果,正常狀況是使用一個抽象的方法,讓別人繼承這個類的時候實現方法,實現本身的功能。每一次都須要實現的話,重複度太大了。

public YxUser processCustomer(Consumer<YxUser> makeCustomerHappy) {
        YxUser c = new YxUser();
        makeCustomerHappy.accept(c);
        return c;
    }
    
    /** * 在processCustomer中直接使用Lambda將重寫的部分寫進去,這樣避免了大量的繼承實現。 */
    YxUser yx = new FactoryImpl().processCustomer(e -> e.setUsername("楊鑫"));

複製代碼

3.觀察者模式

​ 一個事件發生,主題去通知全部觀察者,觀察者會根據本身觀察的東西進行操做。

在這裏插入圖片描述

實現觀察者模式:①觀察者Observer接口;②實現Observer接口的各類觀察者。③實現觀察者註冊和通知的Subject接口和其實現。

使用Lambda,在簡單的觀察者模式來講,能夠避免第②部分的操做,可是觀察者的邏輯過多時,感受lambda不適應。

mark

demo地址:github.com/fireshoot/j…

相關文章
相關標籤/搜索