java8之流的基本使用

天道酬勤      2022-04-20     517

关键词:

概述

流(stream())是java8的一个新特性,主要的作用就是将各种类型的集合转换为流,然后的方便迭代数据用的.例如:

//将List类型的集合转换为流
list.stream()

转换为流之后可以进行一系列的迭代操作,比自己去拿出list的值一个个操作要方便的多.

使用流的好处

  • 声明性 -- 更简洁、更易读
  • 可复合 -- 更灵活
  • 可并行 -- 性能更好

流的使用方法介绍

使用流之前,必须先对函数式接口、lambda表达式和方法引用有一些了解,如果您不具备这方面知识,请移驾lambda表达式&方法引用.

使用的oracle默认的emp表的字段:

public class Emp {
    private BigDecimal empno;

    private boolean trueOrFalse;

    private String ename;

    private String job;

    private BigDecimal mgr;

    private Date hiredate;

    private Double sal;

    private BigDecimal comm;

    private BigDecimal deptno;

    public BigDecimal getEmpno() {
        return empno;
    }

    public void setEmpno(BigDecimal empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename == null ? null : ename.trim();
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job == null ? null : job.trim();
    }

    public BigDecimal getMgr() {
        return mgr;
    }

    public void setMgr(BigDecimal mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSal() {
        return sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public BigDecimal getComm() {
        return comm;
    }

    public void setComm(BigDecimal comm) {
        this.comm = comm;
    }

    public BigDecimal getDeptno() {
        return deptno;
    }

    public void setDeptno(BigDecimal deptno) {
        this.deptno = deptno;
    }


}

1.过滤

得到工资在1000以上的员工的集合:

       //得到list集合
        List<Emp> listEmp = empService.listEmp();
        /*
        *  1. listEmp.stream() 将集合转换为流,
        *  这样就可以用流的方法对集合进行迭代.
        *
        *  2.filter方法.里面的emp相当于拿到集合中的每一个emp进行操作,
        *    结果要返回一个Boolean值
        *
        *  3. .collect(toList()),实际是.collect(Collectors.toList()).
        * */
        List<Emp> result = listEmp.stream().filter(emp -> emp.getSal() > 1000).collect(toList());
        System.out.println("result = " + result);
        
打印输出:
result = [Emp(empno=7499, trueOrFalse=false, ename=ALLEN, job=SALESMAN, mgr=7698, hiredate=Fri Feb 20 00:00:00 CST 1981, sal=1600.0, comm=300, deptno=30), Emp(empno=7521, trueOrFalse=false, ename=WARD, job=SALESMAN, mgr=7698, hiredate=Sun Feb 22 00:00:00 CST 1981, sal=1250.0, comm=500, deptno=30), Emp(empno=7566, trueOrFalse=false, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20), Emp(empno=7654, trueOrFalse=false, ename=MARTIN, job=SALESMAN, mgr=7698, hiredate=Mon Sep 28 00:00:00 CST 1981, sal=1250.0, comm=1400, deptno=30), Emp(empno=7698, trueOrFalse=false, ename=BLAKE, job=MANAGER, mgr=7839, hiredate=Fri May 01 00:00:00 CST 1981, sal=2850.0, comm=null, deptno=30), Emp(empno=7782, trueOrFalse=false, ename=CLARK, job=MANAGER, mgr=7839, hiredate=Tue Jun 09 00:00:00 CST 1981, sal=2450.0, comm=null, deptno=10), Emp(empno=7788, trueOrFalse=false, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=Sun Apr 19 00:00:00 CDT 1987, sal=3000.0, comm=null, deptno=20), Emp(empno=7839, trueOrFalse=false, ename=KING, job=PRESIDENT, mgr=null, hiredate=Tue Nov 17 00:00:00 CST 1981, sal=5000.0, comm=null, deptno=10), Emp(empno=7844, trueOrFalse=false, ename=TURNER, job=SALESMAN, mgr=7698, hiredate=Tue Sep 08 00:00:00 CST 1981, sal=1500.0, comm=0, deptno=30), Emp(empno=7876, trueOrFalse=false, ename=ADAMS, job=CLERK, mgr=7788, hiredate=Sat May 23 00:00:00 CDT 1987, sal=1100.0, comm=null, deptno=20), Emp(empno=7902, trueOrFalse=false, ename=FORD, job=ANALYST, mgr=7566, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=3000.0, comm=null, deptno=20), Emp(empno=7934, trueOrFalse=false, ename=MILLER, job=CLERK, mgr=7782, hiredate=Sat Jan 23 00:00:00 CST 1982, sal=1300.0, comm=null, deptno=10)]

结果返回了所有工资在1000以上的员工.

分布介绍一下方法,.filter()会对集合中的每一个元素都执行一次括号内的操作:

.filter(emp -> emp.getSal() > 1000)

我们追一下filter方法,可以看到:

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

返回的是一个流,参数是一个Predicate.再追这个参数:

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);

@FunctionalInterface注解说明这是一个函数式接口.并不是有了这个注释才说明这是一个函数式接口,而是只有一个抽象方法的接口,就是函数式接口.

接口的方法:

boolean test(T t);

参数是任意类型,返回值Boolean类型.

也就是说.filter()方法,可以传递任意类型的参数,返回值必须是Boolean类型的,只要满足这两个条件,都可以传到filter方法中.

我们传递的,参数emp,返回值emp.getSal()>1000是个Boolean值,所以满足:

.filter(emp -> emp.getSal() > 1000)

.collect(toList())将结果转换为一个list集合,真正地写法为:

.collect(Collectors.toList());

因为我在这个类中使用了静态导入:

import static java.util.stream.Collectors.*;

所以前面的Collectors可以不写.

2.截断

有时候,得到一个集合只需要其中的几位,这时候可以使用截断:

List<Emp> result = listEmp.stream()
                .filter(emp -> emp.getSal() > 1000)
                //截取3位
                .limit(3)
                .collect(toList());
        System.out.println("result = " + result);

打印输出:result = [Emp(empno=7499, trueOrFalse=false, ename=ALLEN, job=SALESMAN, mgr=7698, hiredate=Fri Feb 20 00:00:00 CST 1981, sal=1600.0, comm=300, deptno=30), Emp(empno=7521, trueOrFalse=false, ename=WARD, job=SALESMAN, mgr=7698, hiredate=Sun Feb 22 00:00:00 CST 1981, sal=1250.0, comm=500, deptno=30), Emp(empno=7566, trueOrFalse=false, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20)]

.limit()方法,可以截取指定的位数

3.跳过元素

刚刚是截断3位,这次我们输出跳过3位的结果,使用.skip():

 List<Emp> result = listEmp.stream()
                .filter(emp -> emp.getSal() > 1000)
                //跳过3位
                .skip(3)
                .collect(toList());
        System.out.println("result = " + result);
打印输出:result = [Emp(empno=7654, trueOrFalse=false, ename=MARTIN, job=SALESMAN, mgr=7698, hiredate=Mon Sep 28 00:00:00 CST 1981, sal=1250.0, comm=1400, deptno=30), Emp(empno=7698, trueOrFalse=false, ename=BLAKE, job=MANAGER, mgr=7839, hiredate=Fri May 01 00:00:00 CST 1981, sal=2850.0, comm=null, deptno=30), Emp(empno=7782, trueOrFalse=false, ename=CLARK, job=MANAGER, mgr=7839, hiredate=Tue Jun 09 00:00:00 CST 1981, sal=2450.0, comm=null, deptno=10), Emp(empno=7788, trueOrFalse=false, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=Sun Apr 19 00:00:00 CDT 1987, sal=3000.0, comm=null, deptno=20), Emp(empno=7839, trueOrFalse=false, ename=KING, job=PRESIDENT, mgr=null, hiredate=Tue Nov 17 00:00:00 CST 1981, sal=5000.0, comm=null, deptno=10), Emp(empno=7844, trueOrFalse=false, ename=TURNER, job=SALESMAN, mgr=7698, hiredate=Tue Sep 08 00:00:00 CST 1981, sal=1500.0, comm=0, deptno=30), Emp(empno=7876, trueOrFalse=false, ename=ADAMS, job=CLERK, mgr=7788, hiredate=Sat May 23 00:00:00 CDT 1987, sal=1100.0, comm=null, deptno=20), Emp(empno=7902, trueOrFalse=false, ename=FORD, job=ANALYST, mgr=7566, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=3000.0, comm=null, deptno=20), Emp(empno=7934, trueOrFalse=false, ename=MILLER, job=CLERK, mgr=7782, hiredate=Sat Jan 23 00:00:00 CST 1982, sal=1300.0, comm=null, deptno=10)]

4.映射

.map()方法,得到集合中每个元素的某个信息时使用,比如我们只要拿到集合中的所有员工的姓名:

List<Emp> listEmp = empService.listEmp();
        List<String> resultList = listEmp.stream()
                //得到集合中的某一个元素
                .map(emp -> emp.getEname())
                .collect(toList());
        System.out.println("resultList = " + resultList);
打印输出:
resultList = [aaa, SMITH, ALLEN, WARD, JONES, MARTIN, BLAKE, CLARK, SCOTT, KING, TURNER, ADAMS, JAMES, FORD, MILLER]

当你不知道流中的方法,.filter()或者是.map()抑或是其他任何流中的方法里面需要传递什么参数返回什么结果的时候,就可以向之前一样,追踪一下源码,我们以.map()为例:

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

map方法里面需要的是另一种函数式接口,Function,我们继续追:

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

可以看到里面的apply方法,是传入任意类型的T,返回的是不同的任意类型的R,在看我们的代码,传入Emp,返回String,满足!:

.map(emp -> emp.getEname())

5.扁平的映射

就是.flatMap()方法.这个方法的作用是将一个流中的每个流都换成另一个值,然后把所有流连接起来成为一个流.

举个具体的例子.将["Hello","World"] 变为["H","e","l","w","r","d"]

List<String> collect = list.stream()
                .map(txt -> txt.split(""))
                .flatMap(txt -> Arrays.stream(txt))
                .distinct().collect(toList());

使用.split("")方法返回两个数组{"H","e","l","l","o"}和{"W","o""r","d"}

.map(txt -> txt.split(""))

Arrays.stream()方法将两个数组变成两个流,flatMap将两个流合成一个流.

.flatMap(txt -> Arrays.stream(txt))

6. 查找和匹配

Stream API提供allMatch、anyMatch、noneMatch、findFirst和findAny方法.判断集合中是否有要匹配的值.

匹配

anyMatch,如果有一个匹配就返回true,看看是否有员工叫SMITH:

 List<Emp> list = empService.listEmp();
        boolean result = list.stream().anyMatch(emp -> emp.getEname().equals("SMITH"));
        System.out.println("result = " + result);

输出结果: result = true

allMatch,全部匹配才返回true:

List<Emp> list = empService.listEmp();
        boolean result = list.stream().allMatch(emp -> emp.getEname().equals("SMITH"));
        System.out.println("result = " + result);

输出: result = false

noneMatch,如果没有匹配到返回true:

List<Emp> list = empService.listEmp();
        boolean result = list.stream().noneMatch(emp -> emp.getEname().equals("SMITH"));
        System.out.println("result = " + result);
        
打印输出: result = false

anyMatch、allMatch和noneMatch这三个操作都用到了我们所谓的短路,这就是大家熟悉的Java中的&&和||运算符短路在流中的版本.

这里要说明一点,stream分为中间操作终端操作,像map()、filter()、flatMap()等就是中间操作,他们可以继续调用其它中间操作,想一个流水线一样.而终端操作就是无法再调用其它流的方法的方法,例如 刚刚的三个match方法和collect()方法等.

查找

findAny方法返回当前流中的任意元素,可以将filter和findAny配合使用:

List<Emp> list = empService.listEmp();
        Optional<Emp> result = list.stream().filter(emp -> emp.getSal() > 2000).findAny();
        System.out.println("result = " + result);
        System.out.println("result.get() = " + result.get());
        
打印输出:
result = Optional[Emp(empno=7566, trueOrFalse=false, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20)]
result.get() = Emp(empno=7566, trueOrFalse=false, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20)

细心的朋友可能发现了,这里返回的结果是一个:

Optional<Emp> result

有了Optional类以后就可以和nullPointerException说88了,这是一个容器类,代表一个值存在或不存在

findFirst方法和findAny差不多,但是如果这是一个并行流,findAny返回的就是最先找到的任意一个,而findFirst返回的是第一个.

7. 归约

reduce()方法,求所有员工的工资之和:

List<Emp> list = empService.listEmp();
        Double reduce = list.stream().map(emp -> emp.getSal())
        //这里的第一个参数0代表初始值
                .reduce((double) 0, (a, b) -> a + b);
        System.out.println("得到的工资之和是:"+reduce);

打印输出: 得到的工资之和是:29026.0

注意reduce()方法中的第一个参数0也可以不写但是返回的就是一个Optional类型的结果.

List<Emp> list = empService.listEmp();
        Optional<Double> reduce = list.stream().map(emp -> emp.getSal())
                //这里的第一个参数0代表初始值
                .reduce((a, b) -> a + b);
        System.out.println("得到的工资之和是:"+reduce);

还有一点需要注意的是,如果reduce执行的是乘法,那么初始值就应该是1而不是0.

8.最大值和最小值

使用reduce也可以获得最大值和最小值:

        List<Emp> list = empService.listEmp();
        Optional<Double> reduce = list.stream().map(emp -> emp.getSal())
                //获得最大值
                .reduce(Double::max);
        System.out.println("最大工资是::"+reduce.get());
        
打印输出: 最大工资是::5000.0

求最小值只需把Double::max换成Double::min.(这是方法引用,看不懂的移步上面的另一篇博客)

9 由值创建流

使用Stream.of方法创建一个流.

Stream<String> values = Stream.of("aaa","bbb","ccc");
        values.map(String::toUpperCase).forEach(System.out::println);
        //等价于下面的
        values.map(value -> value.toUpperCase()).forEach(value -> System.out.println(value));

两个写法的效果是一样的,一个使用的是lambda表达式一个使用的是方法引用.

10. 由数组创建流

int[] values = {1,2,3};
        IntStream stream = Arrays.stream(values);
        //sum方法求和
        int sum = stream.sum();
        System.out.println("sum = " + sum);
        
打印输出:sum = 6

11.由函数生成流:创建无限流

Stream.iterate和Stream.generate可以创建无穷无尽的流,但是一般会使用limit()方法来限制创建流的大小,以避免打印无数的值,例子:

Stream.iterate(0,n-> n+2)
                .limit(10)
                .forEach(System.out::println);
                
输出:
0
2
4
6
8
10
12
14
16
18

第一次输出的是0,共输出十次.

generate与iterate类似.

总结

  • 可以使用filter、distinct、skip和limit对流做筛选和切片.
  • 使用map和flatMap提取或转换流中的元素.
  • 使用findFirst和findAny方法查找流中的元素.可以用allMatch、noneMatch、anyMatch方法让流匹配给定的谓词.
  • 这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流.
  • 使用reduce方法将流中所有的元素迭代合成一个结果,可以求出最大值或最小值

基本语法之流程控制之关键字:break和continue

break和continue关键字的使用   使用范围 循环中使用的作用(不同点) 相同点  break: switch-case   循环结构中 结束当前循环 关键字后面不能声明执行语句 continue: 循环结构中 结束当次循环 关键字后面不能声明... 查看详情

2_基本语法之流程控制之循环结构

1.循环结构的四要素  ①初始化条件  ②循环条件--->是boolean类型  ③循环体  ④迭代条件  说明:通常情况下,循环结束都是因为②中循环条件返回false了。2.三种循环结构:  2.1for循环结构  for(①;②;④)  ... 查看详情

方法引用的基本使用(代码片段)

1、例子中用到的函数式接口packagecom.ly.demo.java8;/***@authorliuyang*@create2020-05-1723:16*/@FunctionalInterfacepublicinterfaceIntArrayBuilderint[]buildIntArray(intlen);packagecom.ly.demo.java8;/***@authorliuyang*@create2020-05-1721:57*///函数接口,有且仅有一个抽象方法的... 查看详情

3.go语言基础之流程控制(代码片段)

1.ifelse(分支结构)1.1if判断基本语法if条件判断的格式如下:if表达式1分支1elseif表达式2分支2else分支3Go语言规定与if匹配的左括号必须与if和表达式放在同一行,放在其他位置会触发编译错误。同理,与else匹配的也必须与else写在同... 查看详情

java程序猿之流程控制与数组

  分支语句  if括号里的只能是一个逻辑表达式,即这个表达式返回的值只能是true或false。  代码块用花括号括起来,一个代码块通常被当成一个整体来执行(除非遇到return、break、continue等关键字,或者遇到异常)。  ... 查看详情

go语言基础之流程控制(代码片段)

流程控制是每种编程语言控制逻辑走向和执行次序的重要部分,流程控制可以说是一门语言的“经脉”。Go语言中最常用的流程控制有if和for,而switch和goto主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制... 查看详情

java8使用

...impleDateFormatter都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。以下为一些常用时间对象:Instant:表示时刻,不直接对应年月日信息,需要通过时区转换LocalD 查看详情

java基础之流程控制

一、顺序结构    顺序结构的程序语句只能被执行一次。如果您想要同样的操作执行多次,,就需要使用循环结构。  if-else-if语句语法:  if(条件){        当条件为true时,执行大括号内... 查看详情

设置曲面样式之流域(代码片段)

QQ网友1109743151求助,在设置流域后不显示。代码中貌似没有什么问题,经过测试,发现使用手工设置曲面样式过程中,如果显示流域,会有一个曲面分析的过程发生,于是在代码中添加了曲面分析的代码,之后即可正常显示流域... 查看详情

puppet之流程控制介绍

1 概述puppet流程控制语句有四种,if,case,selector,unless(3.7版本后引入),由于unless是if单分支语句取反,因此,本文重点介绍if,case,selector三种语法的使用备注:本文的相关内容摘自文章:https://www.dwhd.org/20150609_155030.html2 if语... 查看详情

java8新特性

...I详解[3、Java8新特性终极指南]简单的使用看完新特性概述基本上就够了,详细了解StreamAPI可以看看详解,想整体了解可以看看<写给大忙人看的JAVASE8> 查看详情

怎么在java8的map中使用stream

...Stream的概念,那么我们怎么在Map中使用Stream呢?基本概念Map有key,value还有表示key,value整体的Entry。创建一个Map:M 查看详情

java8新特性——lambda表达式之基本语法&自定义函数式接口(代码片段)

1.写在前面目前我们学习Java主要用到的应该就是Java8了,或者说大部分企业当前使用的也是Java8。那么既然Java8的应用如此之广泛,一定有一些亮点所在:Lambda表达式函数式接口方法引用与构造器引用StreamAPI接口中的默... 查看详情

java8新特性——lambda表达式之基本语法&自定义函数式接口(代码片段)

1.写在前面目前我们学习Java主要用到的应该就是Java8了,或者说大部分企业当前使用的也是Java8。那么既然Java8的应用如此之广泛,一定有一些亮点所在:Lambda表达式函数式接口方法引用与构造器引用StreamAPI接口中的默... 查看详情

java学习笔记之流文件的操作

ava学习笔记之流、文件的操作对于一些基础的知识,这里不再过多的解释,简单的文件查询过滤操作packagecom.wfu.ch08;importjava.io.File;importjava.io.FilenameFilter;publicclassTest1{publicstaticvoidmain(String[]args){Filefile=newFile("F:\2017-20 查看详情

java8基础知识泛型的约束与局限性

...现的,所以存在某些局限(大多来自于擦除)。不能使用基本类型实例化类型参数类型参数都是类,要用包装器将基本类型包装才可以作为类型参数(原因在于擦除类型后Object类不能存储基本类型的值)。当包装器类不能接受类... 查看详情

shell脚本之流程控制

  shell脚本之流程控制shell脚本之流程控制条件语句条件判断循环语句for,while,untilfor循环while循环until循环循环控制语句continue循环控制语句break循环控制shift命令创建无限循环while的特殊用法for的特殊用法select循环与菜单s... 查看详情

阿花宝宝java基础笔记之流程控制

1.各循环可互相嵌套   一般不超过三层   外层循环变量变化一次,内层循环变量要变化一遍  注意点:循环次数。(内层循环次数*外层循环次数)2.break语句的使用 break语句用于终止某个循环,使... 查看详情