乐趣区

关于java: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. 流式操作和汇合操作的区别:

    • 按需解决 ,汇合 全解决
    • 流主攻 数据处理 ,汇合主攻 数据存储
    • 简洁 ,汇合
    • 外部迭代 (只迭代一次,完后流会隐没),汇合 内部迭代(可始终迭代)

后记

最初,感激大家的观看,谢谢

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

退出移动版