Stream流學習加強

集合與數組Stream

Stream流的遍歷寫法

	public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三丰");
        
        list.stream().filter((e) -> {//lambda標準格式寫法
            return e.startsWith("張");
        }).filter((e)->{return e.length()==3;}).forEach((e)->{
            System.out.println(e);
        });//方法調用完畢返回一個流,還可以繼續調用,鏈式調用

        list.stream()
          	.filter(s -> s.startsWith("張"))//lambda省略格式寫法
            .filter(s -> s.length() == 3)
            .forEach(System.out::println);//用下方法引用
    }

直接閱讀代碼的字面意思即可完美展示無關邏輯方式的語義:獲取流、過濾姓張、過濾長度爲3、逐一打印。代碼中並沒有體現使用線性循環或是其他任何算法進行遍歷,我們真正要做的事情內容被更好地體現在代碼中。

獲取流,單列集合通過stream方法,數組通過靜態of方法

單列集合通過stream方法,獲取流

首先,java.util.Collection接口中加入了default方法stream()用來獲取流,所以其所有實現類均可獲取流。

 public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // ...
        Stream<String> stream1 = list.stream();//變成一個流.var

        Set<String> set = new HashSet<>();
        // ...
        Stream<String> stream2 = set.stream();

        Vector<String> vector = new Vector<>();
        // ...
        Stream<String> stream3 = vector.stream();
    }

雙列集合先得到單列集合,再獲取流,雙列集合不能直接來,只能間接來

java.util.Map接口不是Collection的子接口,且其K-V數據結構不符合流元素的單一特徵Stream,所以獲取對應的流需要分key、value或entry等情況:

public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();//以下都是雙列集合裏面的單列集合
        // ...
        Stream<String> keyStream = map.keySet().stream();//鍵的集合,單列集合
        Stream<String> valueStream = map.values().stream();//值的集合,單列集合
        Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();//鍵值對對象集合
    }

流靜態of方法,傳入數組,獲取流

如果使用的不是集合而是數組,由於數組對象不可能添加默認方法,所以Stream接口中提供了靜態方法of,使用很簡單:

import java.util.stream.Stream;

public class Demo06GetStream {
    public static void main(String[] args) {
        String[] array = { "張無忌", "張翠山", "張三丰", "張一元" };
        Stream<String> stream = Stream.of(array);
        
        Stream<String> ss = Stream.of("fbb", "jjjjjg", "jjj");
    }
}

備註:of方法的參數其實是一個可變參數,本質是一個數組,所以支持數組。

常用方法

流模型的操作(方法)很豐富,這裏介紹一些常用的API。這些方法可以被分成兩種:

  • 終結方法:返回值類型不再是Stream接口自身類型的方法,因此不再支持類似StringBuilder那樣的鏈式調用。本小節中,終結方法包括countforEach方法。
  • 非終結方法:返回值類型仍然是Stream接口自身類型的方法,因此支持鏈式調用。(除了終結方法外,其餘方法均爲非終結方法。)

過濾:filter,過濾條件爲真,產生一個新的流

可以通過filter方法將一個流轉換成另一個子集流。方法簽名:

Stream<T> filter(Predicate<? super T> predicate);//根據條件過濾元素,到一個新的流裏面,產生一個新的流

該接口接收一個Predicate函數式接口參數(可以是一個Lambda或方法引用)作爲篩選條件。

Predicate接口

java.util.stream.Predicate函數式接口,其中唯一的抽象方法爲:

boolean test(T t);

該方法將會產生一個boolean值結果,代表指定的條件是否滿足。如果結果爲true,那麼Stream流的filter方法將會留用元素;如果結果爲false,那麼filter方法將會捨棄元素。

基本使用

Stream流中的filter方法基本使用的代碼如:

	public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
        Stream<String> result = original.filter((s) -> {return s.startsWith("張");});//標準格式
        //Stream<String> result = original.filter(s -> s.startsWith("張"));//省略格式
    }

在這裏通過Lambda表達式來指定了篩選的條件:必須姓張。

統計個數:count,統計流中元素個數

正如舊集合Collection當中的size方法一樣,流提供count方法來數一數其中的元素個數:

long count();

該方法返回一個long值代表元素個數(不再像舊集合那樣是int值)。基本使用:

	public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
        Stream<String> result = original.filter(s -> s.startsWith("張"));
        System.out.println(result.count()); // 2
    }

取用前幾個:limit,限制截取,有限截取,產生新的流

limit方法可以對流進行截取,只取用前n個。方法簽名:

Stream<T> limit(long maxSize);

參數是一個long型,如果集合當前長度大於參數則進行截取;否則不進行操作。基本使用:

import java.util.stream.Stream;

public class Demo10StreamLimit {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
        Stream<String> result = original.limit(2);
        System.out.println(result.count()); // 2
    }
}

跳過前幾個:skip,跳過前幾個,產生新的流

如果希望跳過前幾個元素,可以使用skip方法獲取一個截取之後的新流:

Stream<T> skip(long n);

如果流的當前長度大於n,則跳過前n個;否則跳過所有,將會得到一個長度爲0的空流。基本使用:

import java.util.stream.Stream;

public class Demo11StreamSkip {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
        Stream<String> result = original.skip(2);
        System.out.println(result.count()); // 1
    }
}

(數據轉換)映射:map,把調用方法的流變成另一個流,即把流的元素類型轉換改變即可

如果需要將流中的元素映射到另一個流中,可以使用map方法。方法簽名:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

該接口需要一個Function函數式接口參數,可以將當前流中的T類型數據轉換爲另一種R類型的流。

Function接口

此前我們已經學習過java.util.stream.Function函數式接口,其中唯一的抽象方法爲:

R apply(T t);//通過T類型得到R類型東西,把一個東西變成轉換成另一個東西,綁定死了,t綁定r,對應關係,映射

這可以將一種T類型轉換成爲R類型,而這種轉換的動作,就稱爲“映射”。//理解,t綁定r,對應關係,映射

基本使用

Stream流中的map方法基本使用的代碼如:

public static void main(String[] args) {
       	Stream<String> original = Stream.of("10", "12", "18");
      //Stream<Integer> result = original.map(s -> Integer.valueOf(s));
      //把字符串元素變成整數,新的流
        Stream<Integer> result = original.map(new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return Integer.valueOf(s);
            }
        });
        System.out.println(result.collect(Collectors.toList())); // [10, 12, 18]
}

這段代碼中,map方法的參數通過方法引用,將字符串類型轉換成爲了int類型(並自動裝箱爲Integer類對象)。

組合(合併):concat,靜態方法,傳入兩個流,進行合併

如果有兩個流,希望合併成爲一個流,那麼可以使用Stream接口的靜態方法concat

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

備註:這是一個靜態方法,與java.lang.String當中的concat方法是不同的。

該方法的基本使用代碼如:

public class Demo12StreamConcat {
    public static void main(String[] args) {
        Stream<String> streamA = Stream.of("張無忌");
        Stream<String> streamB = Stream.of("張翠山");
        Stream<String> result = Stream.concat(streamA, streamB);
    }
}

遍歷: forEach,消費每一個元素,一般用於打印元素

雖然方法名字叫forEach,但是與增強for循環中的“for-each”暱稱不同,該方法並不保證元素的逐一消費動作,在流中是被有序執行的

void forEach(Consumer<? super T> action);

該方法接收一個Consumer接口函數,會將每一個流元素交給該函數進行處理。例如:

public static void main(String[] args) {
        Stream<String> stream = Stream.of("張無忌", "張三丰", "周芷若");
      //stream.forEach(System.out::println);
    	stream.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
    }

在這裏,方法引用System.out::println就是一個Consumer函數式接口的實例(對象)。

總結:函數拼接與終結方法

在上述介紹的各種方法中,凡是返回值仍然爲Stream接口的爲函數拼接方法,它們支持鏈式調用;而返回值不再爲Stream接口的爲終結方法,不再支持鏈式調用。如下表所示:

方法名 方法作用 方法種類 是否支持鏈式調用
count 統計個數 終結
forEach 逐一處理 終結
filter 過濾 函數拼接
skip 跳過前幾個 函數拼接
map 映射 函數拼接
concat 組合 函數拼接
limit 取用前幾個 函數拼接

Stream流轉換爲集合或者數組

對流操作完成之後,如果需要將其結果進行收集,例如獲取對應的集合、數組等,如何操作?

流的collect收集方法(自動選你想要的),傳入收集工具類調用方法轉換爲集合

直接用,下面的這一波,有空了解一下,看看就好:

Stream流提供collect方法,其參數需要一個java.util.stream.Collector<T,A, R>接口對象來指定收集到哪種集合中。

幸運的是,java.util.stream.Collectors收集器工具類提供一些方法,可以作爲Collector接口的實例:

  • public static <T> Collector<T, ?, List<T>> toList():轉換爲List集合。
  • public static <T> Collector<T, ?, Set<T>> toSet():轉換爲Set集合。

下面是這兩個方法的基本使用代碼:

	 public static void main(String[] args) {
        Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
        //流收集方法,傳入收集工具類調用方法轉換爲集合
        List<String> arr = stream.collect(Collectors.toList());//流已經變成一個集合,到達終點不能再變,流一去不復返!!!
        for (String s : arr) {
            System.out.println(s);
        }

        //所以,下面不能同時來要註釋一下,否則報IllegalStateException: stream has already been operated upon or closed
//        Set<String> set = stream.collect(Collectors.toSet());
//        for (String s : set) {
//            System.out.println(s);
//        }
         
          //java.util.stream.Collectors收集器工具類,更優美的字符串拼接
        String[] arr = {"12","13","15","16"};
        String str = Arrays.stream(arr)     //間隔        前綴      後綴
                .collect(Collectors.joining("@","[","]"));
//                .collect(Collectors.joining("@"));
        System.out.println(str); //[12@13@15@16]
         
    }

流調用方法轉換爲數組

Stream提供toArray方法來將結果放到一個數組中,返回值類型是Object[]的:

Object[] toArray();

其使用場景如:

import java.util.stream.Stream;

public class Demo16StreamArray {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
        Object[] arr = stream.toArray();
        
        for (Object o : arr) {
            System.out.println(o);
        }
    }
}

併發流,流併發,都是同一個意思,讓流實現併發操作

對於Stream流來說,這很簡單。

流的併發方法,跟下面的單列集合的併發流效果,本質上是一樣的,都是併發

Stream的父接口java.util.stream.BaseStream中定義了一個parallel方法:

S parallel();//parallel併發的意思

只需要在流上調用一下無參數的parallel方法,那麼當前流即可變身爲支持併發操作的流,返回值仍然爲Stream類型。例如:

import java.util.stream.Stream;

public class Demo13StreamParallel {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(10, 20, 30, 40, 50).parallel();//流的併發方法
        //流的併發方法,跟下面的單列集合的併發流效果,本質上是一樣的,都是併發
        stream.forEach((e)->{
            System.out.println(e);//每次運行的結果有可能都不太一樣,併發操作,一會執行這個,一個執行那個
        });
    }
}

單列集合的併發流方法

在通過集合獲取流時,也可以直接調用parallelStream方法來直接獲取支持併發操作的流。方法定義爲:

default Stream<E> parallelStream() {...}

應用代碼爲:

import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo13StreamParallel {
    public static void main(String[] args) {
        ArrayList<String> al = new ArrayList<>();
        al.add("wo");
        al.add("ai");
        al.add("ni");

        Stream<String> stream = al.parallelStream();//單列集合的併發流方法
        //每次運行的結果有可能都不太一樣,併發操作,一會執行這個,一個執行那個
        stream.forEach(System.out::println);//方法引用,打印參數,這裏是的參數是元素,即打印元素
    }
}

擴展:流調用方法轉換爲數組,並傳入數組構造器引用,指定對應類型的數組

有了Lambda和方法引用之後,可以使用toArray方法的另一種重載形式傳遞一個IntFunction<A[]>的函數,繼而從外面指定泛型參數。方法簽名:

<A> A[] toArray(IntFunction<A[]> generator);//需要一個數組的構造器引用,構造一個指定類型的數組出來!!!

有了它,上例代碼中不再侷限於Object[]結果,而可以得到String[]結果:

import java.util.stream.Stream;

public class Demo17StreamArray {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
      //String[] arr = stream.toArray(value -> new String[value]);
        String[] arr = stream.toArray(String[]::new);//new String[];
        
        for (String s : arr) {
            System.out.println(s);
        }
    }
}
//既然數組也是有構造器的,那麼傳遞一個數組的構造器引用,即可構造一個對應類型的數組出來

在Lambda標準格式()->{}的基礎上,使用省略寫法的規則爲:

  1. 小括號內參數的類型可以省略;
  2. 如果小括號內有且僅有一個參數,則小括號可以省略;
  3. 如果大括號內有且僅有一個語句,則無論是否有返回值,都可以省略大括號、return關鍵字及語句分號。

stream流其他教材博客資料:

stream.toArray(數組的構造器)lambda表達式:構造器引用資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章