Java8之Stream流(五)映射流

Java8之Stream流(一)基础体验
Java8之Stream流(二)关键知识点
Java8之Stream流(三)缩减操作
Java8之Stream流(四)并行流
Java8之Stream流(六)收集
Java8之Stream流(七)流与迭代器

经过了前面四篇文章的学习,相信大家对Stream流已经是相当的熟悉了,同时也掌握了一些高级功能了,如果你之前有阅读过集合框架的基石Collection接口,是不是在经过前面的学习,以前看不懂的东西,突然之间就恍然大悟了呢?今天我们的主角是Stream流里面的映射。由于之前,映射并没有再我们的Demo,例子中出现过,所以对大家来说可能会稍微有一点点陌生的,但通过这一篇文章,我相信能解决你的疑问。

在正式开始之前,我和大家继续说说流API操作,不知道大家有没有注意到,其实我们所有的流API操作都是针对流中的元素进行的,并且都是基于同一流里面的,大家有没有这样的疑问,怎么样把一个流的元素弄到另一个流里面呢?怎么把流中的一些满足条件的元素放到一个新流里面呢?通过这一节,你将会掌握解决刚才问题的本领。另外再提一点,如果流操作只有中间操作,没有终端操作,那么这些中间操作是不会执行的,换句话说,只有终端操作才能触发中间操作的运行。

我们为什么需要映射?

因为在很多时候,将一个流的元素映射到另一个流对我们是非常有帮助的。比如有一个包含有名字,手机号码和钱的数据库构成的流,可能你只想要映射钱这个字段到另一个流,这时候可能之前学到的知识就还不能解决,于是映射就站了出来了。另外,如果你希望对流中的元素应用一些转换,然后把转换的元素映射到一个新流里面,这时候也可以用映射。

我们先来看看流API库给我们提供了什么样的支持

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);//line2
    IntStream mapToInt(ToIntFunction<? super T> mapper);//line3
    LongStream mapToLong(ToLongFunction<? super T> mapper);//line4
    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);//line5
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);//line6
    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);//line7
    LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);//line8
    DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);//line9
}

我和大家分析一个最具有一般性的映射方法map(),相信大家就能举一反三了,map()定义如下,

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

其中,R指定新流的元素类型,T指定调用流的元素类型,mapper是完成映射的Function实例,被称为映射函数,映射函数必须是无状态和不干预的(大家对这二个约束条件应该很熟悉了吧)。因为map()方法会返回一个新流,因此它是一个中间操作。

Functionjava.util.function包中声明的一个函数式接口,声明如下:

@FunctionalInterface
public interface Function<T, R> {
     R apply(T t);
}

在map()的使有过程中,T是调用流的元素类型,R是映射的结果类型。其中,apply(T t)中的t是对被映射对象的引用,被返回映射结果。下面我们将上一篇中的例子进行变形,用映射来完成他:

假设List里面有三个Integer类型的元素分别为1,2,3。现在的需求是分别让List里面的每个元素都放大两倍后,再求积。这个需求的正确答案应该是48;

private static void learnMap() {
     List<Integer> lists = new ArrayList<>();
        lists.add(1);
        lists.add(2);
        lists.add(3);
     //使用并行流来处理
     Integer product = lists.parallelStream().reduce(1, (a, b) -> a *  (b * 2),
                                                        (a, b) -> a * b);
     System.out.println("product:" + product);//48

     //使用映射来处理 
     //Integer productMap = lists.parallelStream().map((a) -> a * 2).reduce(1, (a, b) -> a * b);
     Stream<Integer> productNewMapStream = lists.parallelStream().map((a) -> a * 2);
     Integer productMap = productNewMapStream.reduce(1, (a, b) -> a * b);
     System.out.println("productMap:" + productMap);//48
}

与使用并行流不同,在使用映射处理的时候,元素扩大2倍发生时机不一样了,使用并行流元素扩大是在缩减的过程当中的,而使用映射处理时,元素扩大是发生在映射过程中的。因此映射过程完程之后,不需要reduce()提供合并器了。

上面的这个例子还是简单了一点,下面再举一个例子,王者荣耀团队经济计算:

#玩家使用的英雄以及当前获得的金币数
public class HeroPlayerGold {
    /** 使用的英雄名字 */
    private String hero;
    /** 玩家的ID */
    private String player;
    /** 获得的金币数 */
    private int gold;

    public HeroPlayerGold(String hero, String player, int gold) {
        this.hero = hero;
        this.player = player;
        this.gold = gold;
    }
  //省略get/set/toString
}

#玩家获得的金币数
public class Gold {
    /** 获得的金币数 */
    private int gold;

    public Gold(int gold) {
        this.gold = gold;
    }
 //省略get/set/toString
}

#测试类
public class Main {
    public static void main(String[] args) {
        learnMap2th();
    }

    private static void learnMap2th() {
        List<HeroPlayerGold> lists = new ArrayList<>();
        lists.add(new HeroPlayerGold("盖伦", "RNG-Letme", 100));
        lists.add(new HeroPlayerGold("诸葛亮", "RNG-Xiaohu", 300));
        lists.add(new HeroPlayerGold("露娜", "RNG-MLXG", 300));
        lists.add(new HeroPlayerGold("狄仁杰", "RNG-UZI", 500));
        lists.add(new HeroPlayerGold("牛头", "RNG-Ming", 500));

        //计算团队经济
        int teamMoney = lists.stream()
                .map(player -> new Gold(player.getGold()))//note1
                .mapToInt(Gold::getGold)
                .reduce(0, (a, b) -> a + b);
        System.out.println("团队经济:" + teamMoney);//1700


        //计算团队经济2
        double teamMoney2 = lists.stream()
                .mapToDouble(HeroPlayerGold::getGold)
                .reduce(0, (a, b) -> a + b);
        System.out.println("团队经济:" + teamMoney2);//1700.0
    }
}

代码应该不难理解,通过代码,大家应该知道我们假设的场景了。我们的RNG去参加王者荣耀比赛了,像这种团队游戏,观众在经济方面关注更多的可能是团队经济,而不是个人经济。在我们HeroPlayerGold类里面存有明星玩家,使用的英雄,和这局比赛某个玩家当前获得的金币数,我们另有一个专们管理金币的Gold类,我们第一种计算团队经济的方式,使把HeroPlayerGold里面的gold字段转换到Gold里面了 //note1 ,这里产生的新流只包含了原始流中选定的gold字段,因为我们的原始流中包含了heroplayergold,三个字段,我们只选取了gold字段(因为我们只关心这个字段),所以其它的两个字段被丢弃了。然后从新流取出Gold里面的gold字段并把他转成一个IntStream,然后我们就要以通过缩减操作完成我们的团队经济计算了,第一种方式,大家需要好好理解,理解了,我相信你们的项目中,很多很多地方可以用得上了,再也不需要动不动就查数据库了,怎样效率高怎样来,只是一种建议。第二种只是快速计算团队经济而已,没什么值得讲的。

接下来讲一下他的扩展方向:大家还记得我在第二篇中介绍中间操作概念的时候吗?中间操作会产生另一个流。因此中间操作可以用来创建执行一系列动作的管道。我们可以把多个中间操作放到管道中,所以我们很容易就创建出很强大的组合操作了,发挥你的想象,打出你们的组合拳;

我现在举一个例子:比如现在相统计团队里面两个C位的经济占了多少,代码看起来可能就是这样了:

 private static void learnMap2th() {
        List<HeroPlayerGold> lists = new ArrayList<>();
        lists.add(new HeroPlayerGold("盖伦", "RNG-Letme", 100));
        lists.add(new HeroPlayerGold("诸葛亮", "RNG-Xiaohu", 300));
        lists.add(new HeroPlayerGold("露娜", "RNG-MLXG", 300));
        lists.add(new HeroPlayerGold("狄仁杰", "RNG-UZI", 500));
        lists.add(new HeroPlayerGold("牛头", "RNG-Ming", 500));
        
        //计算两个C位的经济和
        lists.stream()
                .filter(player-> "RNG-Xiaohu".equals(player.getPlayer()) || "RNG-UZI".equals(player.getPlayer()))
                .map(player->new Gold(player.getGold()))
                .mapToInt(Gold::getGold)
                .reduce((a,b)->a+b)
                .ifPresent(System.out::println);//800
    }

大家有没有感觉,这种操作怎么带有点数据库的风格啊?其实在创建数据库查询的时候,这种过滤操作十分常见,如果你经常在你的项目中使用流API,这几个条件算什么?等你们把流API用熟了之后,你们完全可以通过这种链式操作创建出非常复杂的查询,合并和选择的操作。

通过前面的例子,我们已经把map()mapToInt()mapToLong()mapToDouble都讲了。那么剩下的就是flatMap()方法了。本来想让大家自行去理解这个方法的,因为怕这篇文章写得太长了。但是后面想想,还是我来给大家分析一下吧。

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

通过前面的学习我们知道mapper是一个映射函数,它和map()方法也一样也会返回一个新流,我们把返回的新流称为映射流。我们提供的映射函数会处理原始流中的每一个元素,而映射流中包含了所有经过我们映射函数处理后产生的新元素。加粗部份需要重点理解。

我们来看一下源码对flatMap()的注释:The flatMap() operation has the effect of applying a one-to-many transformation to the elements of the stream, and then flattening the resulting elements into a new stream.大意就是:flatMap()操作能把原始流中的元素进行一对多的转换,并且将新生成的元素全都合并到它返回的流里面。根据我们所学的知识,他的这种一对多的转换功能肯定就是映射函数提供的,这一点没有疑问吧!然后源码的注释上面还提供了一个例子,通过注释加例子,我相信大家都能非常清楚地理解flatMap()了。

    /* <p>If {@code orders} is a stream of purchase orders, and each purchase
     * order contains a collection of line items, then the following produces a
     * stream containing all the line items in all the orders:
     * <pre>{@code
     *     orders.flatMap(order -> order.getLineItems().stream())...
     * }</pre>
     */

如果orders是一批采购订单对应的流,并且每一个采购订单都包含一系列的采购项,那么orders.flatMap(order -> order.getLineItems().stream())...生成的新流将包含这一批采购订单中所有采购项。我们用伪代码来就更加清晰了 Stream<Orders<OrderItem>> ====>Stream<OrderItem>。大家能理解了吗?还没理解?再来一个例子:

 private static void learnFlatMap() {
        //(广州  深圳  上海  北京)的全拼的一些组合,下面我们就把每一个城市都划分一下
        List<String> citys = Arrays.asList("GuangZhou ShangHai", "GuangZhou ShenZhen",
                "ShangHai ShenZhen", "BeiJing ShangHai", "GuangZhou BeiJing", "ShenZhen BeiJing");

        //这里打印的数组对应的地址
        citys.stream().map(mCitys -> Arrays.stream(mCitys.split(" "))).forEach(System.out::println);//note1

        System.out.println();

        //流里面的元素还是一个数组
        citys.stream()
                .map(mCities -> Arrays.stream(mCities.split(" ")))//流里面的每个元素还是数组
                .forEach(cities ->cities.forEach(city-> System.out.print(city+" ")));//note2

        System.out.println();
        System.out.println();

        //直接一个flatMap()就把数组合并到映射流里面了
        citys.stream().flatMap(mCities->Arrays.stream(mCities.split(" "))).forEach(System.out::println);//note3

        System.out.println();

        //使用distinct()方法去重!
        citys.stream().flatMap(mCities->Arrays.stream(mCities.split(" "))).distinct().forEach(System.out::println);//note4

    }

其中 //note1 处是无法打印元素的,使用map()打印元素的方式在 //note2 ,原因也在注释中交待了,但是使用了flatMap()方法后,直接就可以打印了 //note3 ,到这里,应该就能理解**如果orders是一批采购订单对应的流,并且每一个采购订单都包含一系列的采购项,那么orders.flatMap(order -> order.getLineItems().stream())...生成的新流将包含这一批采购订单中所有采购项。**了吧。最后 //note4 是一个去重的方法,大家运行一遍吧。

小结一下

通过这一篇文章,相信大家对流API中的映射已经不再陌生了,其实最需要注意的一个点是,map()和flatMap()的区别,我也一步步地带着大家理解和应用了。其实在流API这一块中,大家单单掌握概念是没什么用的,一定要去实战了,一个项目里面,集合框架这种东西用得还是特别多的,用到集合框架的大部份情况,其实都可以考虑一下用Stream流去操作一下,不仅增加效率,还可以增加业务流程的清晰度。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 这篇关于java stream的文章写的特别好,转载一下,以备自己查看。转载自Java 8 中的 Streams ...
    小白小白啦阅读 510评论 0 2
  • Jav8中,在核心类库中引入了新的概念,流(Stream)。流使得程序媛们得以站在更高的抽象层次上对集合进行操作。...
    仁昌居士阅读 3,625评论 0 6
  • 本文采用实例驱动的方式,对JAVA8的stream API进行一个深入的介绍。虽然JAVA8中的stream AP...
    浮梁翁阅读 25,729评论 3 50
  • 手术做完了,我也回来了,坐在这回忆下和你的所有事,我应该是7月9号入司的,入司呢天没见到你,只听师父说咱们小组里还...
    ayan的马一一阅读 391评论 0 0