Java8中Stream流式操作指南之入门篇

目录
  • 简介
  • 正文
    • 1. 流是什么
    • 2. 老板,上栗子
    • 3. 流的操作步骤
    • 4. 流的特点
    • 5. 流式操作和集合操作的区别:
  • 总结

简介

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

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

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

正文

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. 流的操作步骤

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

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

流程如下图:

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

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

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

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

因为每一个中间操作都是返回一个流(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);

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

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

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

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

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

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

总结

流是什么:

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

到此这篇关于Java8中Stream流式操作的文章就介绍到这了,更多相关Java8中Stream流式操作内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java8中的Stream流式操作教程之王者归来

    前言 相对于Java8之前的Java的相关操作简直是天差地别,Java8 的流式操作的出现,也很大程度上改变了开发者对于Java的繁琐的操作的印象,从此,Java也走向了函数式编程的道路! 1 流的创建 1.1 流的创建方法 既然需要聊聊流的操作,那么,首先还是先看看怎么创建流. 创建流的方法有三种,分别是:Stream.of().Stream.iterate().Stream.generate(),然后,分别看一下这三个方法的声明. static <T> Stream<T> of

  • Java8特性之用Stream流代替For循环操作详解

    目录 准备一个实体类 准备一个List集合 传统的for循环 使用Stream流 先声明筛选条件,在遍历 Stream操作 嵌套循环(2层) 准备一个实体类 public class Student { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int get

  • Java8之Stream流代替For循环操作

    Stream流代替For循环进行输出可以使代码更简洁. 需求:根据姓名获取员工信息 1.建立实体类:Emp public class Emp { private String id; private String name; public Emp(String id, String name) { this.id=id; this.name=name; } public String getId() { return id; } public void setId(String id) { th

  • 浅谈java8 stream flatMap流的扁平化操作

    概念: Steam 是Java8 提出的一个新概念,不是输入输出的 Stream 流,而是一种用函数式编程方式在集合类上进行复杂操作的工具.简而言之,是以内部迭代的方式处理集合数据的操作,内部迭代可以将更多的控制权交给集合类.Stream 和 Iterator 的功能类似,只是 Iterator 是以外部迭代的形式处理集合数据的操作. 在Java8以前,对集合的操作需要写出处理的过程,如在集合中筛选出满足条件的数据,需要一 一遍历集合中的每个元素,再把每个元素逐一判断是否满足条件,最后将满足条件

  • java8新特性 stream流的方式遍历集合和数组操作

    前言: 在没有接触java8的时候,我们遍历一个集合都是用循环的方式,从第一条数据遍历到最后一条数据,现在思考一个问题,为什么要使用循环,因为要进行遍历,但是遍历不是唯一的方式,遍历是指每一个元素逐一进行处理(目的),而并不是从第一个到最后一个顺次处理的循环,前者是目的,后者是方式. 所以为了让遍历的方式更加优雅,出现了流(stream)! 1.流的目的在于强掉做什么 假设一个案例:将集合A根据条件1过滤为子集B,然后根据条件2过滤为子集C 在没有引入流之前我们的做法可能为: public cl

  • Java8中Stream流式操作指南之入门篇

    目录 简介 正文 1. 流是什么 2. 老板,上栗子 3. 流的操作步骤 4. 流的特点 5. 流式操作和集合操作的区别: 总结 简介 流式操作也叫做函数式操作,是Java8新出的功能 流式操作主要用来处理数据(比如集合),就像泛型也大多用在集合中一样(看来集合这个小东西还是很关键的啊,哪哪都有它) 下面我们主要用例子来介绍下,流的基操(建议先看下lambda表达式篇,里面介绍的lambda表达式.函数式接口.方法引用等,下面会用到) 正文 1. 流是什么 流是一种以声明性的方式来处理数据的AP

  • JDK1.8新特性Stream流式操作的具体使用

    一. 前言 随着Java的发展,越来越多的企业开始使用JDK1.8 版本.JDK1.8 是自 JDK1.5之后最重要的版本,这个版本包含语言.编译器.库.工具.JVM等方面的十多个新特性.本次文章将着重学习Stream. Stream 是JDK1.8 中处理集合的关键抽象概念,Lambda 和 Stream 是JDK1.8新增的函数式编程最有亮点的特性了,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找.过滤和映射数据等操作.使用Stream API 对集合数据进行操作,就类似于使用SQ

  • Java8中Stream的一些神操作

    Java8对集合提供了一种流式计算的方式,这种风格将要处理的元素集合看 作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如 筛选, 排序,聚合等. Stream API 基本都是返回Stream本身,这样多个操作可以串联成一个管 道, 如同流式风格(fluent style). 这样做可以对操作进行优化, 比 如延迟执行(laziness)和短路( short-circuiting) stream() 为集合创建串行流 parallelStream() 为集合创建并行流 pri

  • ASP.NET Core SignalR中的流式传输深入讲解

    前言 什么是流式传输? 流式传输是这一种以稳定持续流的形式传输数据的技术. 流式传输的使用场景 有些场景中,服务器返回的数据量较大,等待时间较长,客户端不得不等待服务器返回所有数据后,再进行相应的操作.这时候使用流式传输,可以将服务器数据碎片化,当每个数据碎片读取完成之后,就只传输完成的部分,而不需要等待所有数据都读取完成. SignalR SignalR是一个.NET Core/.NET Framework的开源实时框架. SignalR的可使用Web Socket, Server Sent

  • node.js中stream流中可读流和可写流的实现与使用方法实例分析

    本文实例讲述了node.js中stream流中可读流和可写流的实现与使用方法.分享给大家供大家参考,具体如下: node.js中的流 stream 是处理流式数据的抽象接口.node.js 提供了很多流对象,像http中的request和response,和 process.stdout 都是流的实例. 流可以是 可读的,可写的,或是可读可写的.所有流都是 events 的实例. 一.流的类型 node.js中有四种基本流类型: 1.Writable 可写流 (例:fs.createWriteS

  • java8中Stream的使用以及分割list案例

    一.Steam的优势 java8中Stream配合Lambda表达式极大提高了编程效率,代码简洁易懂(可能刚接触的人会觉得晦涩难懂),不需要写传统的多线程代码就能写出高性能的并发程序 二.项目中遇到的问题 由于微信接口限制,每次导入code只能100个,所以需要分割list.但是由于code数量可能很大,这样执行效率就会很低. 1.首先想到是用多线程写传统并行程序,但是博主不是很熟练,写出代码可能会出现不可预料的结果,容易出错也难以维护. 2.然后就想到Steam中的parallel,能提高性能

  • Java8中Stream的使用方式

    目录 前言: 1. 为什么有经验的老手更倾向于使用Stream 2. Stream 的使用方式 3. Stream 的创建 4. Stream 中间操作 5. Stream 终止操作 6. Stream 特性 前言: 相信有很多刚刚入坑程序员的小伙伴被一些代码搞的很头疼,这些代码让我们既感觉到很熟悉,又很陌生的感觉.我们很多刚入行的朋友更习惯于使用for循环或是迭代器去解决一些遍历的问题,但公司里很多老油子喜欢使用Java8新特性Stream流去做,这样可以用更短的代码实现需求,但是对于不熟悉的

  • Java8中Stream的详细使用方法大全

    目录 一.概述 1.使用流的好处 2.流是什么? 二.分类 三.Stream的创建 1.通过 java.util.Collection.stream() 方法用集合创建流 2.使用 java.util.Arrays.stream(T[]array)方法用数组创建流 3.使用 Stream的静态方法:of().iterate().generate() 四.Stream API简介 1.遍历/匹配(foreach/find/match) 2.按条件匹配filter 3.聚合max.min.count

  • 深入解析Jdk8中Stream流的使用让你脱离for循环

    学习要求: 知道一点儿函数式接口和Lambda表达式的基础知识,有利于更好的学习. 1.先体验一下Stream的好处 需求:给你一个ArrayList用来保存学生的成绩,让你打印出其中大于60的成绩. public static void main(String[] args) { ArrayList<Integer> arrList = new ArrayList<>(); for (int i = 0; i < 100; i++) { arrList.add((int)

随机推荐