Java8中的Stream流式操作 - 入门篇

作者:汤圆

个人博客:javalover.cc

前言

之前总是朋友朋友的叫,感觉有套近乎的嫌疑,所以后面还是给大家改个称呼吧

因为大家是来看东西的,所以暂且叫做官人吧(灵感来自于民间流传的四大名著之一《金瓶梅》)

官人们好啊,我是汤圆,今天给大家带来的是《Java8中的Stream流式操作 - 入门篇》,希望有所帮助,谢谢

文章纯属原创,个人总结难免有差错,如果有,麻烦在评论区回复或后台私信,谢啦

简介

流式操作也叫做函数式操作,是Java8新出的功能

流式操作主要用来处理数据(比如集合),就像泛型也大多用在集合中一样(看来集合这个小东西还是很关键的啊,哪哪都有它)

下面我们主要用例子来介绍下,流的基操(建议先看下lambda表达式篇,里面介绍的lambda表达式函数式接口方法引用等,下面会用到)

先来看下目录

目录

  1. 流是什么

  2. 老板,上栗子

  3. 流的操作步骤

  4. 流的特点

  5. 流式操作和集合操作的区别

正文

1. 流是什么

流是一种以声明性的方式来处理数据的API

什么是声明性的方式?

就是只声明,不实现,类似抽象方法(多态性)

2. 老板,上栗子

举个栗子

下面我们举个栗子,来看下什么是流式操作,然后针对这个栗子,引出后面的相关概念

需求筛选年龄大于1的猫(猫的1年≈人的5年),并按年龄递增排序,最后提取名字单独存放到列表中


public class BasicDemo {
    public static void main(String[] args) {
      // 以下猫的名字均为真名,非虚构
        List<Cat> list = Arrays.asList(new Cat(1, "tangyuan"), new Cat(3, "dangdang"), new Cat(2, "milu"));
        // === 旧代码 Java8之前 ===
        List<Cat> listTemp = new ArrayList<>();
        // 1. 筛选
        for(Cat cat: list){
            if(cat.getAge()>1){
                listTemp.add(cat);
            }
        }
        // 2. 排序
        listTemp.sort(new Comparator<Cat>() {
            @Override
            public int compare(Cat o1, Cat o2) {
                // 递增排序
                return Integer.compare(o1.getAge(), o2.getAge());
            }
        });
        // 3. 提取名字
        List<String> listName = new ArrayList<>();
        for(Cat cat: listTemp){
            listName.add(cat.getName());
        }
        System.out.println(listName);
        
        // === 新代码 Java8之后 ===
        List<String> listNameNew = list.stream()
                    // 函数式接口 Predicate的 boolean test(T t)抽象方法
                .filter(cat -> cat.getAge() > 1)
                                // lambda表达式的方法引用
                      .sorted(Comparator.comparingInt(Cat::getAge))
                    // 函数式接口 Funtion的 R apply(T t)抽象方法
                .map(cat-> cat.getName())
                  // 收集数据,把流转为集合List
                .collect(Collectors.toList());
        System.out.println(listNameNew);
    }
}
class Cat{
    int age;
    String name;

    public Cat(int age, String name) {
        this.age = age;
        this.name = name;
    }
    // 省略getter/setter
}

可以看到,用了流式操作,代码简洁了很多(秒哇)

Q:有的官人可能会想,这跟前面lambda表达式的组合操作有点像啊。

A:你说对了,确实只是像,区别还是很大的。因为lambda表达式的组合操作其实还是属于直接针对集合的操作;

文末会讲到直接操作集合和流式操作的区别,这里先跳过

下面我们基于这个栗子,来分别介绍涉及到的知识点

3. 流的操作步骤

我们先忽略旧版的集合操作(后面介绍流和集合的区别时再说),先来介绍流的操作(毕竟流才是今天的主角嘛)

主角

流的操作分三步走:创建流、中间操作、终端操作

流程如下图:

流的操作流程

这里我们要关注一个很重要的点:

在终端操作开始之前,中间操作不会执行任何处理,它只是声明执行什么操作;

你可以想象上面这个流程是一个流水线:我们这里做个简化处理

  1. 目的:先告诉你,我们要加工瓶装的水(先创建流,告诉你要处理哪些数据)
  2. 再针对这些瓶子和水,来搭建一个流水线:固定瓶子的夹具、装水的水管、拧盖子的爪子、装箱的打包器(中间操作,声明要执行的操作)
  3. 最后按下启动按钮,流水线开始工作(终端操作,开始根据中间操作来处理数据)
流水线

因为每一个中间操作都是返回一个流(Stream),这样他们就可以一直组合下去(我好像吃到了什么东西?),但是他们的组合顺序是不固定的,流会根据系统性能去选择合适的组合顺序

我们可以打印一些东西来看下:

List<Cat> list = Arrays.asList(new Cat(1, "tangyuan"), new Cat(3, "dangdang"), new Cat(2, "milu"));
List<String> listNameNew = list.stream()
  .filter(cat -> {
    System.out.println("filter: " + cat);
    return cat.getAge() > 1;
  })
  .map(cat-> {
    System.out.println("map:" + cat);
    return cat.getName();
  })
  .collect(Collectors.toList());

输出如下:

filter: Cat{age=1}
filter: Cat{age=3}
map:Cat{age=3}
filter: Cat{age=2}
map:Cat{age=2}

可以看到,中间操作的filter和map组合到一起交叉执行了,尽管他们是两个独立的操作(这个技术叫作循环合并

这个合并主要是由流式操作根据系统的性能来自行决定的

既然讲到了循环合并,那下面捎带说下短路技巧

短路这个词大家应该比较熟悉(比如脑子短路什么的),指的是本来A->B->C是都要执行的,但是在B的地方短路了,所以就变成了A->C了

这里的短路指的是中间操作,由于某些原因(比如下面的limit),导致只执行了部分,没有全部去执行

我们来修改下上面的例子(加了一个中间操作limit):

List<Cat> list = Arrays.asList(new Cat(1, "tangyuan"), new Cat(3, "dangdang"), new Cat(2, "milu"));
List<String> listNameNew = list.stream()
  .filter(cat -> {
    System.out.println("filter: " + cat);
    return cat.getAge() > 1;
  })
  .map(cat-> {
    System.out.println("map:" + cat);
    return cat.getName();
  })
  // 只加了这一行
  .limit(1)
  .collect(Collectors.toList());

输出如下:

filter: Cat{age=1}
filter: Cat{age=3}
map:Cat{age=3}

可以看到,因为limit(1)只需要一个元素,所以filter过滤时,只要找到一个满足条件的,就会停止过滤操作(后面的元素就放弃了),这个技巧叫做短路技巧

这个就很大程度上体现了中间操作的组合顺序带来的优点:需要多少,处理多少,即按需处理

4. 流的特点

特点有三个:

  • 声明性:简洁,易读,代码行数大大减少(每天有代码行数要求的公司除外)
  • 可复合:更灵活,各种组合都可以(只要你想,只要流有)
  • 可并行:性能更好(不用我们去写多线程,多好)

5. 流式操作和集合操作的区别:

现在我们再来回顾下开头例子中的集合操作:筛选->排序->提取

List<Cat> listTemp = new ArrayList<>();
// 1. 筛选
for(Cat cat: list){
  if(cat.getAge()>1){
    listTemp.add(cat);
  }
}
// 2. 排序
listTemp.sort(new Comparator<Cat>() {
  @Override
  public int compare(Cat o1, Cat o2) {
    // 递增排序
    return Integer.compare(o1.getAge(), o2.getAge());
        /**
    * Q:为啥不用减法 return o1.getAge() - o2.getAge()?
    * A:因为减法会有数据溢出的风险
    *    如果o1.getAge()为20亿,o2.getAge()为-2亿,那么结果就会超过int的极限21亿多
    **/ 
  }
});
// 3. 提取名字
List<String> listName = new ArrayList<>();
for(Cat cat: listTemp){
  listName.add(cat.getName());
}
System.out.println(listName);

可以看到跟流式操作不一样的有两点:

  1. 集合操作中有一个listTemp临时变量(流式操作没),
  2. 集合操作一直都在处理数据(而流式操作是直到最后一步的终端操作才会去处理数据),依次筛选->排序->提取名字,是顺序执行的

下面我们用表格来列出区别,应该会直观点

流式操作 集合操作
功能 处理数据为主 存储数据为主
迭代方式 内部迭代(只迭代一次),只需声明,不需要实现,流内部自己有实现) 外部迭代(可一直迭代)需要自己foreach
处理数据 直到终端操作,才会开始真正处理数据(按需处理) 一直都在处理数据(全部处理)

用生活中的例子来对比的话,可以用电影来比喻

流就好比在线观看,集合就好本地观看(下载到本地)

总结

  1. 流是什么:
    • 流是一种以声明性的方式来处理数据的API
    • 流是从支持数据处理操作生成的元素序列
      • 源:数据的来源,比如集合,文件等(本节只介绍了集合的流式操作,因为用的比较多;后面有空再介绍其他的)
      • 数据处理操作:就是流的中间操作,比如filter, map
      • 元素序列:通过流的终端操作,返回的结果集
  2. 流的操作流程:
    • 创建流 -> 中间操作 -> 终端操作
    • 中间操作只是声明,不真实处理数据,直到终端操作开始才会执行
  3. 循环合并:中间操作会自由组合(流根据系统自己来决定组合的顺序)
  4. 短路技巧:如果中间操作处理的数据已经达到需求,则会立即停止处理数据(比如limit(1),则当处理完1个就会停止处理)
  5. 流式操作和集合操作的区别:
    • 按需处理,集合全处理
    • 流主攻数据处理,集合主攻数据存储
    • 简洁,集合
    • 内部迭代(只迭代一次,完后流会消失),集合外部迭代(可一直迭代)

后记

最后,感谢大家的观看,谢谢

原创不易,期待官人们的三连哟

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

推荐阅读更多精彩内容