1. 程式人生 > 其它 >Stream API ( Java 8 )

Stream API ( Java 8 )

技術標籤:Javajava

Stream API 簡介

Stream’API是Java8中的新特性,基於Lambda表示式,對Collection(集合)的各種操作有了很大的改變,極大的提升了編碼效率和程式碼的可讀性。Stream有序列和並行兩種模式,並行模式會自動建立多個執行緒,使用fork(join)Java7特性,來拆分任務和加速處理過程。Stream是一種類似IO流的東西,但是並不相同,實質是對集合操作的一種高度抽象,而且更重要的是,Stream不是資料結構,是不儲存資料的,資料儲存在底層的集合中,或者根據需要產生出來(例如Stream進行終端操作的時候,生成新的集合)。

Stream抽象概念

Stream將要處理的資料看作一個流,流在管道中流動,可以在管道的節點上對流進行篩選,排序,聚合等操作,也就是資料流在管道中經過中間操作(intermediate operation),最後由終端操作(terminal operation)來得到前面處理過的資料,可以抽象的看作一種類似通過sql查詢資料的方式。

Stream的特徵

  • Stream不是資料結構,是不儲存資料的;
  • Stream的構成資料來源是集合、陣列,I/O channel,generate等等;
  • Stream的中間操作會返還流物件本身,這樣就形成了一個管道;
  • Stream的中間操作類似sql語句,比如filter(篩選), map(元素對映), reduce(聚合), find(查詢), match(匹配), sorted(排序)等;
  • Stream的迭代跟for和foreach在集合外顯式迭代不同,Stream是內部迭代,基於訪問者模式(Visitor)實現;
  • Stream有序列和並行兩種模式;

Stream用法

如何建立Stream流
  • stream() 為集合建立序列流。
  • parallelStream() 為集合建立並行流。
// 程式碼示例
// 集合建立流 Collection介面增加了Steam方法()
List<String> listStr = Arrays.asList("java", "python", "shell", "javaScript"
); Stream<String> steamStr = listStr.stream(); steamStr.forEach(str -> System.out.println(str)); Set<String> setStr = ZoneId.getAvailableZoneIds(); Stream<String> steamSetStr = setStr.stream(); steamSetStr.forEach(str -> System.out.println(str)); // 從陣列建立流 Arrays類中增加stream(T[] array)方法 String[] strs = {"java", "python", "shell", "javaScript"}; Stream<String> stringStream = Arrays.stream(strs); // 從靜態方法中建立 Stream<String> staticStream1 = Stream.of("java", "python", "shell", "javaScript"); staticStream1.forEach(str -> System.out.println(str)); // 使用iterate靜態方法(迭代器的方式)建立無限大小的流,需配合limit使用,防止記憶體溢位 // 會一直增加資料,沒有上限 Stream<Integer> staticStream2 = Stream.iterate(0, x -> x + 1).limit(10); staticStream2.forEach(integer -> System.out.println(integer)); // 使用generate靜態方法建立無限大小的流,需要配合limit使用,防止記憶體溢位 // generate方式建立的無限流最大值是Long.MAX_VALUE Random random = new Random(); Stream<Integer> staticStream3 = Stream.generate(() -> random.nextInt()).limit(10); staticStream3.forEach(integer -> System.out.println(integer)); // 其他方式 java.io.BufferedReader.lines() java.util.stream.IntStream.range() java.nio.file.Files.walk() java.util.Spliterator Random.ints() BitSet.stream() Pattern.splitAsStream(java.lang.CharSequence) JarFile.stream()
Stream流的中間操作

該表的作用是Stream流中間操作方法的引數型別

函式式介面名作用
Function<T, R>接受一個引數T,返回結果R
Predicate接受一個引數T,返回boolean
Supplier不接受任何引數T,返回結果T
Consumer接受一個引數T,不返回結果
UnaryOperator繼承自Function<T,T>,返回相同型別T的結果
BiFunction<T, U, R>接收兩個引數T,U,返回結果R
BinaryOperator繼承自BiFunction<T,T,T>,返回相同型別T的結果
Runnable實際上不接受任何引數,也不返回結果
Comparable實際上是接受兩個相同型別的T,返回int
Callable不接受任何引數,返回結果V
過濾操作
  • filter() 將符合條件的所有元素(資料)轉移到新流中。filter的引數型別是Predicate
// 程式碼示例
List<String> listStr = Arrays.asList("java", "python", "shell", "javaScript");
Stream<String> steamStr = listStr.stream();
steamStr.filter(s -> s.startsWith("j")).forEach(s -> System.out.println(s));
轉換操作
  • map() 將所有資料經過處理之後(可以改變物件型別)轉移到新流中。map的引數型別是Function<T,R>
  • flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 接收一個泛型引數,另一個為Stream流引數,返回的是泛型R,作用是將兩個流合併成一個流輸出
// 程式碼示例
List<String> listStr = Arrays.asList("java", "python", "shell", "javaScript");
Stream<String> steamStr = listStr.stream();
steamStr.map(s -> s.substring(2)).forEach(s -> System.out.println(s));

String[] strs = { "aaa", "bbb", "ccc" };
Arrays.stream(strs).map(str -> str.split("")).forEach(System.out::println);
// [Ljava.lang.String;@5cc7c2a6
// [Ljava.lang.String;@b97c004
// [Ljava.lang.String;@4590c9c3
Arrays.stream(strs).map(str -> str.split("")).flatMap(str -> Arrays.stream(str)).forEach(System.out::println);
// a/a/a/b/b/b/c/c/c

// map操作將strs拆分為三個陣列,stream流中的元素由stream<String>變成了stream<String[]>
// flatMap操作將三個stream<String[]>流合併成一個stream<String>流
提取操作
  • skip(long n) 忽略流中的前n個元素(資料)
  • limit(long maxSize) 獲取流中的maxSize個元素(資料)
// 程式碼示例
List<String> listStr = Arrays.asList("java", "python", "shell", "javaScript");
Stream<String> steamStr = listStr.stream();
steamStr.skip(2).forEach(s -> System.out.println(s));
steamStr.limit(2).forEach(s -> System.out.println(s));
去重操作
  • distinct() 去除流中重複的元素
// 程式碼示例
// 執行時記得註釋其中stringStream執行語句否則會有異常
// java.lang.IllegalStateException: stream has already been operated upon or closed
Stream<String> stringStream = Arrays.asList("java", "python", "shell", "javaScript", "java").stream();
stringStream.distinct().forEach(s -> System.out.println(s));
stringStream.forEach(s -> System.out.println(s));
排序操作
  • sort() 將流中的元素(資料)排序
  • sort(Comparator<? super T> comparator) 將流中元素根據屬性排序
// 程式碼示例
public class test {

    public static List<Emp> emps = new ArrayList<>(10);

    static {
        emps.add(new Emp("1", "xiaoHong1", 20));
        emps.add(new Emp("1", "xiaoHong2", 202));
        emps.add(new Emp("1", "xiaoHong3", 32));
        emps.add(new Emp("1", "xiaoHong4", 45));
        emps.add(new Emp("1", "xiaoHong5", 17));
        emps.add(new Emp("1", "xiaoHong6", 14));
        emps.add(new Emp("1", "xiaoHong7", 65));
        emps.add(new Emp("1", "xiaoHong8", 38));
    }

    public static class Emp {
        private String code;
        private String name;
        private int age;

        public Emp(String code, String name, int age) {
            this.code = code;
            this.name = name;
            this.age = age;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

    /**
     * @param empList 輸出集合中的元素
     */
    public static void println(List<Emp> empList) {
        empList.stream().forEach(emp -> System.out.println(String.format("編號 %S , 姓名 %s , 年齡 %s", emp.getCode(), emp.getName(), emp.getAge())));
    }

    /**
     * 根據物件中年齡進行排序
     */
    public static List<Emp> sortByAge(List<Emp> empList) {
        return empList.stream().sorted(Comparator.comparing(emp -> emp.getAge())).collect(Collectors.toList());
    }

    /**
     * 取出物件中姓名排序,並只返回包含姓名的集合
     */
    public static List<String> sortByName(List<Emp> empList) {
        return empList.stream().map(emp -> emp.getName()).sorted().collect(Collectors.toList());
    }

    public static void main(String[] args) {
        println(emps);
        println(sortByAge(emps));
        sortByName(emps).stream().forEach(s -> System.out.println(s));
    }
}
操作物件
  • peek(Consumer<? super T> action)
// 程式碼示例
    /**
     * @param empList 輸出集合中的元素
     */
    public static void println(List<Emp> empList) {
        empList.stream().forEach(emp -> System.out.println(String.format("編號 %S , 姓名 %s , 年齡 %s", emp.getCode(), emp.getName(), emp.getAge())));
    }
    /**
     * 給年齡大於30歲增加十歲,並返回篩選後的集合
     */
    public static List<Emp> addAge(List<Emp> empList) {
        return empList.stream().filter(emp -> emp.getAge() > 30).peek(emp -> emp.setAge(emp.getAge() + 10)).collect(Collectors.toList());
    }

    public static void main(String[] args) {
        println(addAge(emps));
    }
聚合操作
  • reduce方法
- reduce(BinaryOperator<T> accumulator)
接收一個引數,且引數型別為函式式介面的計算規則,初始值是List集合的第一個值

- reduce(T identity, BinaryOperator<T> accumulator)
引數型別為函式式介面的計算規則,第一引數值是初始值,第二個引數是函式式介面的計算規則

- reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner) 
在多執行緒中使用的,具體不知道(https://segmentfault.com/q/1010000004944450)

// 程式碼示例
// 將流中元素歸約為一個值
Random random = new Random();
List<Integer> randomList = Stream.generate(() -> random.nextInt(10)).limit(10).collect(Collectors.toList());
randomList.stream().forEach(integer -> System.out.println(integer));
System.out.println(randomList.stream().reduce((a, b) -> a + b).get());
System.out.println(randomList.stream().reduce(1,(a, b) -> a + b).longValue());

List<Integer> iterateList = Stream.iterate(0, x -> x + 1).limit(10).collect(Collectors.toList());
iterateList.stream().forEach(integer -> System.out.println(integer));
System.out.println(iterateList.stream().reduce((a, b) -> a - b).get());
System.out.println(iterateList.stream().reduce(1,(a, b) -> a - b).longValue());
  • collect方法
- collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);
傳入三個引數的抽象方法

- collect(Collector<? super T, A, R> collector);
該方法只有一個引數,這個引數先看下stream中的collect操作Collectors靜態工廠類,在這個靜態工廠類裡面,大多都使用三個引數的collect方法實現的。

具體的實現類方法

Collectors靜態工廠類


Collectors靜態工廠類


// 程式碼示例
public class test {

    public static List<Emp> emps = new ArrayList<>(10);

    static {
        emps.add(new Emp("1", "xiaoHong1", 20));
        emps.add(new Emp("1", "xiaoHong2", 202));
        emps.add(new Emp("2", "xiaoHong3", 32));
        emps.add(new Emp("1", "xiaoHong4", 45));
        emps.add(new Emp("2", "xiaoHong5", 17));
        emps.add(new Emp("1", "xiaoHong6", 14));
        emps.add(new Emp("3", "xiaoHong7", 65));
        emps.add(new Emp("3", "xiaoHong8", 38));
    }

    public static class Emp {
        private String code;
        private String name;
        private int age;

        public Emp(String code, String name, int age) {
            this.code = code;
            this.name = name;
            this.age = age;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

    public static void main(String[] args) {
        //轉List
        List<String> nameList = emps.stream().map(emp -> emp.getName()).collect(Collectors.toList());
        //轉Set
        Set<String> nameSet = emps.stream().map(emp -> emp.getName()).collect(Collectors.toSet());
        //轉map,需指定key值,Function.identity()指的是當前物件本身
        Map<String, Emp> nameMap1 = emps.stream().collect(Collectors.toMap(emp -> emp.getName(), emp -> emp));
        Map<String, Emp> nameMap2 = emps.stream().collect(Collectors.toMap(emp -> emp.getName(), Function.identity()));

        //計算集合中元素個數
        long count = emps.stream().collect(Collectors.counting());

        //計算資料 summarizingInt summarizingDouble summarizingLong
        IntSummaryStatistics sum = emps.stream().collect(Collectors.summarizingInt(emp -> emp.getAge()));
        //平均數
        System.out.println(sum.getAverage());
        //總數
        System.out.println(sum.getCount());
        //最大值
        System.out.println(sum.getMax());
        //最小值
        System.out.println(sum.getMin());
        //求和
        System.out.println(sum.getSum());

        //連線字串 字首和字尾只會出現在整個字串的首尾
        String nameStr1 = emps.stream().map(emp -> emp.getName()).collect(Collectors.joining());
        String nameStr2 = emps.stream().map(emp -> emp.getName()).collect(Collectors.joining("中間-"));
        String nameStr3 = emps.stream().map(emp -> emp.getName()).collect(Collectors.joining("中間-", "字首*", "字尾&"));
        System.out.println(nameStr1);
        System.out.println(nameStr2);
        System.out.println(nameStr3);

        //最大值 maxBy 最小值 minBy
        Optional<Integer> maxAge = emps.stream().map(emp -> emp.getAge()).collect(Collectors.maxBy(Comparator.comparing(emp -> emp)));
        Optional<Integer> minAge = emps.stream().map(emp -> emp.getAge()).collect(Collectors.minBy(Comparator.comparing(emp -> emp)));
        System.out.println(maxAge.get());
        System.out.println(minAge.get());

        //聚合操作
        Optional<Integer> ageSum1 = emps.stream().map(emp -> emp.getAge()).collect(Collectors.reducing((x, y) -> x + y));
        Integer ageSum2 = emps.stream().map(emp -> emp.getAge()).collect(Collectors.reducing(1, (x, y) -> x + y));
        System.out.println(ageSum1.get());
        System.out.println(ageSum2);

        //分組操作 根據地址把原List進行分組
        Map<String, List<Emp>> mapGroup = emps.stream().collect(Collectors.groupingBy(emp -> emp.getCode()));
        mapGroup.forEach((s, empList) -> mapGroup.get(s).forEach(emp -> System.out.println(s + ":" + emp.getName())));

        //分割槽操作 需要根據型別進行 需要根據型別指定判斷分割槽
        Map<Boolean, List<Emp>> mapPartitioning = emps.stream().collect(Collectors.partitioningBy(emp -> emp.getAge() > 20));
        mapPartitioning.forEach((b, empList) -> mapPartitioning.get(b).forEach(emp -> System.out.println(b + ":" + emp.getName())));

        List<String> listStr1 = Arrays.asList("java", "python", "shell", "javaScript");
        List<String> listStr2 = Arrays.asList("javaNew", "pythonNew", "shellNew", "javaScriptNew");
        Stream<String> steamSt1 = listStr1.stream();
        Stream<String> steamSt2 = listStr2.stream();
        Stream.concat(steamSt1, steamSt2).forEach(s -> System.out.println(s));
    }

}
stream的終端操作
對集合的流進行遍歷
  • forEach(Consumer<? super T> action)
  • forEachOrdered(Consumer<? super T> action)
// 程式碼示例
// forEach遍歷是無序的遍歷
// forEach遍歷是按照元素的在流中的順序進行遍歷
List<String> strList = Arrays.asList("aaa","bbb","ccc");
strList.parallelStream().forEach(str-> System.out.println(str));
System.out.println();
strList.parallelStream().forEachOrdered(str-> System.out.println(str));
將流轉換為陣列
  • toArray(IntFunction<A[]> generator)
  • toArray()
// toArray()方法的底層呼叫的還是toArray(IntFunction<A[]> generator)方法
// 程式碼示例
List<String> strList = Arrays.asList("aaa", "bbb", "ccc");
Object[] o = strList.stream().toArray();
String[] s = strList.stream().toArray(str -> new String[strList.size()]);
//將集合轉換為陣列進行輸出
Arrays.stream(o).forEach(o1 -> System.out.println(o1));
Arrays.stream(s).forEach(s1 -> System.out.println(s1));
流的長度及流中元素(資料)比較
  • long count() 計算流的長度
  • boolean anyMatch(Predicate<? super T> predicate) 判斷條件中,流中任意一個元素符合,返回true
  • boolean allMatch(Predicate<? super T> predicate) 判斷條件中,流中所有元素都符合,返回true
  • boolean noneMatch(Predicate<? super T> predicate) 判斷條件中,流中任意一個元素都不符合,返回true
// 程式碼示例
public class test {

    public static List<Emp> emps = new ArrayList<>(10);

    static {
        emps.add(new Emp("1", "xiaoHong1", 20));
        emps.add(new Emp("1", "xiaoHong2", 202));
        emps.add(new Emp("1", "xiaoHong3", 32));
        emps.add(new Emp("1", "xiaoHong4", 45));
        emps.add(new Emp("1", "xiaoHong5", 17));
        emps.add(new Emp("1", "xiaoHong6", 14));
        emps.add(new Emp("1", "xiaoHong7", 65));
        emps.add(new Emp("1", "xiaoHong8", 38));
    }

    public static class Emp {
        private String code;
        private String name;
        private int age;

        public Emp(String code, String name, int age) {
            this.code = code;
            this.name = name;
            this.age = age;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

    public static void main(String[] args) {
        System.out.println(emps.stream().filter(emp -> emp.getAge() > 30).count());
        // 5
        System.out.println(emps.stream().allMatch(emp -> emp.getAge() > 30));
        // false
        System.out.println(emps.stream().anyMatch(emp -> emp.getName().startsWith("xiao")));
        // true
        System.out.println(emps.stream().noneMatch(emp -> emp.getAge() < 10));
        // false
    }
}

文章借鑑處

  • https://blog.csdn.net/qq_28410283/article/details/80633710
  • https://miaoxinguo.github.io/java/2016/06/02/java8.2.stream.html
  • https://www.exception.site/java8/java8-stream-tutorial