java8中的Stream

java8提出的函数式编程旨在帮助程序猿们写出更优雅的代码,上文函数式编程基础也介绍了java8新提出的一些函数式接口,通过它们代码貌似已经简洁了一波,但是,代码其实还可以更简洁下,接下来就要开始给大家介绍另一个神器了:Stream,通过它可以进一步利用函数式接口来简化代码了。

注:Stream是java8核心类库中新引入API,它使程序猿们站在更高的抽象层次上对集合进行操作。

常用的流操作

流对象的创建

  1. 通过Stream创建流对象:
    a. 通过empty方法创建空的流对象:

    public static<T> Stream<T> empty() {
        return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
     }
    

    b. 通过of方法创建流对象:

    //创建只含有一个元素的流对象
    public static<T> Stream<T> of(T t) {
        return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
    }
    
    //创建有多个元素的流对象
    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }
    

    c. 通过iterate方法创建流对象:

    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)      {
        Objects.requireNonNull(f);
        final Iterator<T> iterator = new Iterator<T>() {
            @SuppressWarnings("unchecked")
            T t = (T) Streams.NONE;
    
            @Override
            public boolean hasNext() {
                return true;
            }
    
            @Override
            public T next() {
                return t = (t == Streams.NONE) ? seed : f.apply(t);
            }
        };
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
                iterator,
                Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
    }
    

    从源码可以看出,该方法会返回一个无限有序的Stream对象,它的第一个元素是seed,第二个元素是f.apply(seed)...第n个元素是f.apply(n - 1元素的值),它一般与limit等方法一起使用,来获取前多少个元素。
    我们来看看它是如何和limit方法搭配使用的:

    //定义一个第一个元素 = 10, 后一个元素 = 前一个元素 + 2, 长度为20的集合并输出
    Stream.iterate(10, n -> n + 2).limit(20).forEach(System.out::println);
    

    d. 通过generate方法创建流对象,它跟iterate方法有点类似,但是它的每个元素都与前一个元素没有什么关系,并且无序,多用于创建常量Stream或者随机Stream,我们来看看它是如何使用的:

     //定义一个长度为10的随机字符串集合并输出
     Stream.generate(() -> UUID.randomUUID().toString()).limit(10).forEach(System.out::println);
    

    e. 通过concat方法连接两个Stream对象生成一个新的Stream对象:

    //定义一个长度为3的随机字符串集合
    Stream stream1 = Stream.generate(() -> UUID.randomUUID().toString()).limit(3);
    //定义一个第一个元素 = 0, 后一个元素 = 前一个元素 + 2, 长度为3的集合
    Stream stream2 = Stream.iterate(0, n -> n + 2).limit(3);
    //连接stream1和stream2, 遍历输出
    Stream.concat(stream1, stream2).forEach(System.out::println);
    

    我们来看下运行结果:

    ccf394b2-513c-4d4e-8aa4-b824c95918f4
    907b0793-fa19-4cc7-8292-7b7e0818b0a4
    a4b8d4a4-80b2-47e0-973e-80694d355ab2
    0
    2
    4
    
  2. 通过Collection的stream方法和parallelStream方法创建串行或并行的流对象:

//创建串行流对象
default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

//创建并行流对象
default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

我们来看看具体使用:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//创建串行流对象
Stream stream = list.stream();
//创建并行流对象
Stream parallelStream = list.parallelStream();

流对象的创建基本就这些方式创建了,知道怎么创建了,我们接下来看看流常用的方法。

Stream常用方法

map

元素一对一转换,如果你想将一种类型的值转换成另一种类型的值可以用该方法,它的定义如下:

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

从定义可以看出,它接受一个Function参数,元素相关处理转换处理都在Function里实现,同时将处理后的结果放在新的Stream对象中返回。另外,Stream也提供方法针对转换成基本类型(int,long,double)场景:

//转换成int类型
IntStream mapToInt(ToIntFunction<? super T> mapper);
//转换成long类型
LongStream mapToLong(ToLongFunction<? super T> mapper);
//转换成double类型
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

我们来看看它是如何使用的:

//定义一个第一个元素 = "1", 后一个元素 = 前一个元素 + "2", 长度为10的集合
List<String> origins = Stream.iterate("1", n -> n + "2").limit(10).collect(Collectors.toList());
//将origins中的字符串转换成int类型并输出
origins.stream().mapToInt(value -> Integer.valueOf(value)).forEach(System.out::println);
//将origins中的字符串转换成User类型并输出
origins.stream().map(value -> new User(value)).forEach(System.out::println);
flatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

可以理解成元素一对多转换,从定义可以看出,Stream对象中的每个元素经过Function处理后变成一个多元素的Stream对象,然后将多个Stream拼接成一个新的Stream对象返回。我们来个例子:假如我们有一个字符串列表:["I", "Love", "Programing"],要将该单词列表转换成:["I", "L", "o", "v", "e", "P", "r", "o", "g", "r", "a", "m", "i", "n", "g"]:

Stream<String> origins = Stream.of("I", "Love", "Programing");
//字符串转换
List<String> characters = origins.flatMap(value -> Stream.of(value.split(""))).collect(Collectors.toList());
System.out.println(characters);

我们看看输出结果:

[I, L, o, v, e, P, r, o, g, r, a, m, i, n, g]
filter

元素过滤,将符合一定条件的元素过滤出来,它的定义如下:

Stream<T> filter(Predicate<? super T> predicate);

从定义可以看出,它接受一个Predicate参数,元素条件判断都放在Predicate里,同时将符合条件的元素放在新的Stream对象中返回。我们来看看它的使用:

Stream<String> origins = Stream.of("I", "Love", "Programing");
//输出包含"o"的字符串
origins.filter(value -> value.contains("o")).forEach(System.out::println);
match

判断是否有元素符合一定条件,Stream主要提供以下三个方法来实现该操作:

//部分符合,只要有一个元素符合条件时就返回true
boolean anyMatch(Predicate<? super T> predicate);
//全部符合,所有元素符合条件才返回true
boolean allMatch(Predicate<? super T> predicate);
//全部不符合,所有元素都不符合条件时返回true
boolean noneMatch(Predicate<? super T> predicate);
collect

在上面的例子中总是能看到将Stream对象转换成集合的时候用了该方法,是一个及早求值操作。其实它的用法远远不止这些,其他用法后续会出文单独分析,这里就不详加赘述了

注:及早求值惰性求值
判断及早求值与惰性求值很简单,只需要看返回值,如果返回值是Stream,就是惰性求值,如果返回值是另外的值或者为空,则为及早求值。使用流的各种操作的理想方式就是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果。只有在对需要什么样的结果和操作有一定的了解之后才能更有效的作出正确计算。

reduce

从一组值生成另一组值,count/min/max其实都是reduce的操作。我们来个小例子:求和

Stream<Integer> origins = Stream.of(1, 2, 3, 4, 5);
//求和
int sum = origins.reduce(0, (m, n) -> m + n);
System.out.println(sum);

来个图我们来形象的展示下通过reduce进行累加的一个过程:


reduce累加求和示意图

以0作为起点,每一次都将Stream中的元素累加到accumulator,遍历累加到最后一个元素,accumulator里的值其实就是最后的结果。

到这里为止,常用的一些流操作基本上就介绍完了,还有一些去重,排序,查找等简单的内容这里就不做赘述了,有感兴趣的同学可以自行翻阅源码,用法也很简单。

后记

从java8之前的集合操作到java8 Stream对集合的操作,代码更加简单、易读,这一巨大的改变其实是依赖外部迭代 -> 内部迭代这个转变。比如我们遍历求和,java8之前的代码会这样写:

int sum = 0;
for (Integer number : numbers) {
    sum += number;
}

虽然这样做其实也不会有问题,但是还是要正面它的不好处:

  1. 每次对集合做遍历都要写一堆这样子的代码;

  2. 比较难抽象操作,操作和遍历都糅合在一起;

  3. 在for循环的模式下再进行性能改造其实比较困难(比如并行);

  4. 代码易读性较差,特别是有多重嵌套循环时,看代码的人估计就要崩溃了。

当然咯,for循环就是一个封装了迭代(Iterator)的语法糖,这种遍历模式其实也就是外部迭代。

何谓外部迭代,简单点就是显式地进行迭代操作,比如通过Iterator进行迭代,它的过程就是显式调用Iterator对象的hasNext和next方法完成迭代。

那现在我们通过Stream将其变成内部迭代呢:

Stream<Integer> origins = Stream.of(1, 2, 3, 4, 5);
//求和
int sum = origins.reduce(0, (m, n) -> m + n);

所谓的内部迭代,顾名思义,遍历会在集合内部完成,程序猿不需要再去显示的控制循环,也不再需要关心遍历元素的顺序,我们只需要care对元素的操作即可。

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

推荐阅读更多精彩内容

  • Jav8中,在核心类库中引入了新的概念,流(Stream)。流使得程序媛们得以站在更高的抽象层次上对集合进行操作。...
    仁昌居士阅读 3,560评论 0 6
  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,145评论 1 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 丽江古城 天,永远的蓝,云,永远的白! 丽江,东方的威尼斯,我终于来了! 那天晨曦微露,当我一个...
    粲然笑笑阅读 447评论 0 0
  • 图/文 云淡风清 让自己走心的旅行, 走过的每一步都不会白费, 终会变成眼底的清风朗月, 变成心中的星辰大海, 而...
    云淡风清ydfq阅读 362评论 6 11