JDK8新特性Stream流

1. Stream流的出现原因

我们操作集合的时候有时候十分麻烦,如下



image.png

上面的集合操作数据的时候,每一个需求都需要一个新的集合来存放数据,十分麻烦。
为此我们引入Stream流式操作。
注意:Stream和IO流没有任何关系。

2. Stream流式思想概述

Stream流式思想类似与工厂车间的“生产流水线”。Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理,Stream可以看作式流水线上的一个工序,在流水线上,通过多个工序让一个原材料加工成一个产品。


image.png
image.png

看到Stream的filter和map方法参数,都是一个接口,可以使用lambda表达式。
使用方式如下:

public class Demo01Intro {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");

        list.stream().filter((s) -> {
            System.out.println("s1" + s);
            return s.startsWith("张");   // 返回姓张的
        }).filter((s)->{
            System.out.println("s2" +s);
            return s.length() == 3;   // 返回长3的
        }).forEach((s)->{
            System.out.println(s);
        });

    }

且我们运行结果这样的:


image.png

3. 获取流的两种方式

  • 根据Collection获取Stream
  • Stream中的静态方法of获取流
public class Demo02GetStream {
    public static void main(String[] args){
        // 1. 根据Collection获取流
        // list继承Collection
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();

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

        // 根据Map的key和value获取流
        Map<String, String> map = new HashMap<>();
        Stream<String> stream3 = map.keySet().stream();
        Stream<String> stream4 = map.values().stream();
        Stream<Map.Entry<String, String>> stream5 = map.entrySet().stream();

        // 2. 根据Stream的of静态方法
        // public static<T> Stream<T> of(T... values)
        Stream<String> stream6 = Stream.of("a", "b", "c");

        String[] strs = {"aa","bb","cc"};
        Stream<String> stream7 = Stream.of(strs);
        // 基本数据类型的数据的Stream流会把整个数组 int[] 作为一个值传进流中,
        // 而不能操作数组里面的数据
        int[] arr = {1,2,3};
        Stream<int[]> arr1 = Stream.of(arr);
    }
}

4. Stream的注意事项

1. Stream只能操作一次

image.png

2. Stream方法返回的是新的流

image.png

3. Stream方法不调用终结方法,中间的操作不会执行

image.png

5. Stream的常用方法

image.png
  • 终结方法:返回值类型不再是Stream类型的方法,不在支持链式调用。包括count,forEach方法
  • 非终结方法(函数拼接方法):返回值类型仍然式Stream类型的方法,支持链式调用。

5.1 forEach

遍历留里面的所有元素进行操作


image.png

5.2 count

统计流里面元素的个数

long count = list.stream().count();
System.out.println(count);

5.3 filter

用户过滤数据,返回符合过滤条件的数据

list.stream().filter((String str) -> {
            return str.length() == 3;
        }).forEach(s -> System.out.println(s));

5.4 limit

对流进行截取,只取前n个

list.stream().limit(3).forEach(s -> System.out.println(s));

5.5 skip

和limit相反,对流进行截取,跳过前n个获取后面的

list.stream().skip(3).forEach(s -> System.out.println(s));

5.6 map

将流中的元素映射到另一个流中去。将一种类型的流转换成另一种类型的流


map
        // String类型的流变成Integer类型
        Stream<String> original = Stream.of("1", "2", "3");
        Stream<Integer> integerStream = original.map(s -> {
            return Integer.parseInt(s);
        });
        integerStream.forEach(s->{
            System.out.println(s);
        });

5.7 sorted

可以对数据进行排序,是一个函数拼接方法。

// sorted排序
 original.map(s -> {
            return Integer.parseInt(s);
        }).sorted().forEach(System.out::println);

original.map(s -> {
            return Integer.parseInt(s);
        }).sorted((Integer i1, Integer i2)->{
            return i2 - i1;
        }).forEach(System.out::println);

5.8 distinct

如果要去除重复元素,可以使用distinct方法。是一个函数拼接方法。


image.png
Stream<Integer> stream = Stream.of(1, 2, 5, 4, 3, 4, 3, 5, 2);
stream.distinct().forEach(System.out::println);

注意对于自定义类型的数据,无法直接去重,需要我们重写equals和hashcode方法


image.png

5.9 match

如果要判断数据是否匹配指定的条件,可以使用match方法。是一个终结方法。
一共有三个

boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
image.png

5.10 find

如果需要寻找某些数据,可以使用find方法

    Optional<T> findFirst();
    Optional<T> findAny();
Optional<Integer> first = stream.findFirst();
        Integer integer = first.get();
        System.out.println(integer);

5.11 max和min

获取最大和最小值

Optional<Integer> max = stream.max((o1, o2) -> o1 - o2);
System.out.println("最大值" + max.get());

Optional<Integer> min = stream.min((o1, o2) -> o1 - o2);
System.out.println("最小值" + min.get());

5.12 reduce

如果需要将所有数据归纳得到一个数据,可以使用reduce方法。

T reduce(T identity, BinaryOperator<T> accumulator);
T identity:默认值
BinaryOperator<T> accumulator:对数据进行处理的方式
image.png

image.png

image.png
Integer reduce = stream.reduce(0, (x, y) -> {
    System.out.println("x = " + x +", y = " + y);
    return x + y;
});
System.out.println(reduce);

        // 获取最大值
Integer reduce = stream.reduce(0, (x, y) -> {
    return x > y ? x : y;
});
System.out.println(reduce);

5.13 map和reduce的组合使用

通过map的转换,获取到我们需要的数据的流,然后通过reduce的计算,将我们需要的数据给返回。


image.png
image.png
image.png

5.13 mapToInt

如果需要将Stream<Integer>中的Integer转换成int
类型,可以使用mapToInt。
Integer占用的内存比int多,再Stream流操作中,会自动装箱和拆箱。

        // IntStream内部操作的是int类型,可以节省内存,减少自动装箱
        IntStream intStream = Stream.of(1, 2, 5, 4, 3).mapToInt((Integer n) -> {
            return n.intValue();
        });

5.14 concat

如果有两个流,希望合并成一个流,可以使用Stream接口的静态方法concat

  • 两个流合并之后,不能操作之前的流了。
  • 只支持两个流的合并,不支持直接多次合并
        Stream<String> a = Stream.of("a");
        Stream<String> b = Stream.of("b");
        Stream<String> concat = Stream.concat(a, b);
        concat.forEach(System.out::println);

6. 收集Stream流中的结果

对流操作完成后,如需要将流的结果保存到数组或者集合中,可以收集流中的数据。

6.1 收集到集合

        Stream<String> stream = Stream.of("aa", "bb", "cc");

        // 将数据收集到集合中
        List<String> collect1 = stream.collect(Collectors.toList());
        System.out.println(collect1);  // [aa, bb, cc]

        Set<String> collect2 = stream.collect(Collectors.toSet());
        System.out.println(collect1);  // [aa, bb, cc]

        // 收集到指定的ArrayList集合中
        ArrayList<String> collect = stream.collect(Collectors.toCollection(ArrayList::new));

        // 收集到指定的HashSet集合中
        HashSet<String> collect = stream.collect(Collectors.toCollection(HashSet::new));

6.1 收集到数组

        // 转成Object数组,不方便
        Stream<String> stream = Stream.of("aa", "bb", "cc");
        Object[] objects = stream.toArray();
        for (Object object : objects) {
            System.out.println(object);
        }

        // 转成String数组
        String[] strings = stream.toArray(String[]::new);

6.3 对流中数据进行聚合计算

处理是,可以像数据库中的聚合函数一样对某个字段进行操作,如最大值maxBy,最小值minBy,求和summingInt,平均值averagingInt,统计数量counting。
仍然是stream的collect方法参数里面的Collectors的方法。

image.png

6.4 对流中的数据进行分组Collectors.groupingBy

image.png
image.png

6.5 对流中的数据进行多级分组

先根据一个字段进行分组,然后再根据另一个字段进行分组。


image.png

image.png

6.6 对流中的数据进行分区

Collectors.partitioningBy会根据值是否为true,把集合分割成两个列表,一个true列表,一个false列表。


image.png
image.png

6.7 对流中的数据进行拼接

Collectors.joining会根据指定的连接符,将所有元素连接成一个字符串。
一个参数就是拼接处是这个字符。
三个参数则是有前缀和后缀的拼接。


image.png
  • 到集合中:Collectors.toList()/toSet()/toCollection()
  • 到数组中:toArray()/toArray(int[]::new)
  • 聚合计算:Collectors.maxBy/minBy/counting/summingInt/averagingInt
  • 分组:Collectors.groupingBy
  • 分区:Collectors.partitioningBy
  • 拼接:Collectors.joining

7. 并行的Stream流

7.1 串行的Stream流

上面的使用的Stream流都是串行的,就是在一个线程中执行。

7. 2 并行的Stream流的两种方式

        // 1、通过parallelStream直接获取
        ArrayList<String> list = new ArrayList<String>();
        Stream<String> stream1 = list.parallelStream();

        // 2、将串行流转换成并行流
        Stream<Integer> parallel = Stream.of(1, 3, 4, 7).parallel();

7.3 parallelStream的线程安全问题

7.3.1 解决方法1:同步代码块

        ArrayList<Integer> list = new ArrayList<>();
        Object obj = new Object();
        IntStream.rangeClosed(0,1000)
                .parallel()
                .forEach(i->{
                    synchronized (obj){
                        list.add(i);
                    }
                });
        System.out.println(list.size());

7.3.2 解决方法2:线程安全的集合

// 这两个都是线程安全的集合
Vector<Integer> v = new Vector();
List<Integer> integers = Collections.synchronizedList(list);


IntStream.rangeClosed(0,1000)
        .parallel()
        .forEach(i->{
             synchronized (obj){
                  v.add(i);
        }
});
        System.out.println(v.size());

7.3.3 解决方法3:调用Stream流的collect/toArray

        List<Integer> collect = IntStream.rangeClosed(0, 1000)
                .parallel()
                .boxed()
                .collect(Collectors.toList());
        System.out.println(collect.size());
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容