Stream流详解

时间:2024-11-12 08:15:14

原文

一、Stream流的概述

stream流操作是Java 8提供一个重要新特性,它允许开发人员以声明性方式处理集合,其核心类库主要改进了对集合类的 API和新增Stream操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中,能 让代码更加简洁,极大地简化了集合的处理操作,提高了开发的效率和生产力。
同时stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。在Stream中的操作每一次都会产生新的流,内部不会像普通集合操作一样立刻获取值,而是 惰性取值 ,只有等到用户真正需要结果的时候才会执行。 并且对于现在调用的方法,本身都是一种高层次构件,与线程模型无关。因此在并行使用中,开发者们无需再去操 心线程和锁了。Stream内部都已经做好了
关于对Stream流的理解,你可以把他当成工厂中的流水线,每个stream流的操作过程遵循着创建 -->操作 -->获取结果的过程,就像流水线上的节点一样组成一个个链条。除此之外你还可以把他理解成sql的视图,集合就相当于数据表中的数据,获取stream流的过程就是确定数据表的属性和元数据的过程,元数据的每一个元素就是表中的数据,对stream流进行操作的过程就是通过sql对这些数据进行查找、过滤、组合、计算、操作、分组等过程,获取结果就是sql执行完毕之后获取的结果视图一样,深入理解stream流可以让我们使用更加简洁的代码获取自己想要的数据。

二、stream的操作

2.1、stream流创建

获取stream流的方式有许多种,最常用的有以下几种:

  1. Collection接口的stream()或parallelStream()方法;
  2. 静态的()、()方法;
  3. (array, from, to);
  4. 静态的()方法生成无限流,接受一个不包含引元的函数;
  5. 静态的()方法生成无限流,接受一个种子值以及一个迭代函数;
  6. Pattern接口的splitAsStream(input)方法;
  7. 静态的(path)、(path, charSet)方法;
  8. 静态的()方法将两个流连接起来。
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
 
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.stream.Stream;
 
public class StreamTest {
    public static void main(String[] args) {
        //Collection接口的stream()或parallelStream()方法;
        //list流
        Stream<String> listStream = Lists.newArrayList("11", "22", "33").stream();
        //map可以分别获取Entry、key、value的Stream流
        Stream<Map.Entry<Object, Object>> mapEntryStream = Maps.newHashMap().entrySet().stream();
        Stream<Object> mapKsyStream = Maps.newHashMap().keySet().stream();
        Stream<Object> stream = Maps.newHashMap().values().stream();
 
        //静态的()、()方法;
        Stream<Object> ofStream = Stream.of();
        Stream<Object> emptyStream = Stream.empty();
 
        //(array, from, to);
        IntStream intArrayStream = Arrays.stream(new int[]{1, 2, 3});
 
        //静态的()方法生成无限流,接受一个不包含引元的函数;
        Stream<Integer> generateStream = Stream.generate(new Random()::nextInt);
 
        //静态的()方法生成无限流,接受一个种子值(起始值)以及一个迭代函数(增长操作函数);
        Stream<Integer> iterateStream = Stream.iterate(0, x -> x + 1).limit(4);
 
        //Pattern接口的splitAsStream(input)方法;
        Stream<String> patternStream = Pattern.compile("ll").splitAsStream("Hello");
 
        try {
            //静态的(path)、(path, charSet)方法;
            Stream<String> fileStream = Files.lines(Paths.get("d://"));
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        //静态的()方法将两个流连接起来。
        Stream<Integer> concatStream = Stream.concat(generateStream, iterateStream);
    }
}

注意,无限流的存在,侧面说明了流是惰性的,即每当用到一个元素时,才会在这个元素上执行这一系列操作。

2.2、stream的使用

    Stream流接口中定义了许多对于集合的操作方法,总的来说可以分为两大类:中间操作和终端操作:
  1. 中间操作:会返回一个流,通过这种方式可以将多个中间操作连接起来,形成一个调用链,从而转换为另外 一个流。除非调用链后存在一个终端操作,否则中间操作对流不会进行任何结果处理。
  2. 终端操作:会返回一个具体的结果,如boolean、list、integer等。

数据准备如下:

public class Employee {
    //姓名
    private String name;
    //年龄
    private Integer age;
    //性别
    private String sex;
    //薪资
    private Double salary;
    //住址
    private String address;
    //工作地
    private String baseAddress;
    //职位
    private String position;
    //主管
    private Employee employee;
    public Employee(String name, Integer age, String sex, Double salary, String address, String baseAddress, String position, Employee employee) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.salary = salary;
        this.address = address;
        this.baseAddress = baseAddress;
        this.position = position;
        this.employee = employee;
    }
    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
//创建原始数据       
List<Employee> list = new ArrayList<>();
Employee ceo = new Employee("张三", 56, "男", 50000.42D, "浙江杭州", "浙江杭州", "ceo", null);
Employee manager1 = new Employee("李四", 47, "女", 20000.7D, "浙江宁波", "浙江宁波", "经理", ceo);
Employee manager2 = new Employee("王五", 45, "男", 24000.5D, "浙江金华", "浙江金华", "经理", ceo);
Employee employee1 = new Employee("麻六", 27, "女", 7000.6D, "浙江宁波", "广东广州", "售前", manager1);
Employee employee2 = new Employee("孙七", 28, "男", 8000.8D, "浙江宁波", "广东深圳", "售后", manager1);
Employee employee3 = new Employee("赵八", 27, "女", 9500.2D, "浙江杭州", "云南昆明", "售前", manager2);
Employee employee4 = new Employee("钱九", 26, "男", 9000.0D, "浙江杭州", "云南玉溪", "售后", manager2);
list.add(ceo);
list.add(manager1);
list.add(manager2);
list.add(employee1);
list.add(employee2);
list.add(employee3);
list.add(employee4);

2.2.1、中间操作-filter

public class StreamTest {
    public static void main(String[] args) {
        //查找名字为张三的员工信息,并打印出来
        list.stream().filter(employee -> StringUtils.equals("张三", employee.getName())).forEach(employee -> System.out.println(employee.getName()));
    }
}

结果:

张三

filter返回一个Boolean类型的值,true为保留,false为舍弃。

2.2.2、中间操作-map

//收集公司里所有员工的名字,并打印出来
list.stream().map(employee -> {
return employee.getName(); 
}).forEach(name-> System.out.println(name));

结果:

张三
李四
王五
麻六
孙七
赵八
钱九

2.2.3、中间操作-flatMap

//公司加入一个新人,找出公司所有员工中年龄为18的并打印出来
List<Employee> newList = new ArrayList<>();
newList.add(new Employee("新人", 18, "女", 5000.7D, "浙江金华", "浙江金华", "售前", null));
Stream.of(list, newList).flatMap(employees -> employees.stream().filter(employee -> employee.getAge() == 18)).forEach(employee -> System.out.println(employee.getName()));

结果:

新人

2.2.4、中间操作-limit

//获取第一个员工的数据并打印出来
list.stream().limit(1).forEach(employee -> System.out.println(employee.getName()));

结果:

张三

注意:limit索引起始于1,如果传入负值会抛出异常。

2.2.5、中间操作-skip

//跳过前6条数据并打印名字出来
list.stream().skip(6).forEach(employee -> System.out.println(employee.getName()));

结果:

钱九

skip和limit相似都是从1开始,传入负值会抛出异常。

2.2.6、中间操作-distinct

//为了去重在加一个ceo数据
list.add(ceo);
//跳过前6条数据并打印名字出来
list.stream().distinct().forEach(employee -> System.out.println(employee.getName()));

结果:

张三
李四
王五
麻六
孙七
赵八
钱九

2.2.7、中间操作-sorted

//按公司员工的薪资水平排序,并打印名字
list.stream().sorted((e1,e2) -> (int) (e2.getSalary() - e1.getSalary())).forEach(employee -> System.out.println(employee.getName()));

结果:

张三
王五
李四
赵八
钱九
孙七
麻六

2.2.8、中间操作-peek

//打印公司员工的名字和职位
list.stream().peek(employee -> System.out.print(employee.getName() + ":")).forEach(employee -> System.out.println(employee.getPosition()));

张三:ceo
李四:经理
王五:经理
麻六:售前
孙七:售后
赵八:售前
钱九:售后

注意:peek的官方定位是为了debug使用的,他和map功能有些类似,不同的是 map需要有返回值,且返回值可以被stream流接纳,而peek不需要有返回值

2.2.9、中间操作-collect

//把公司员工按照职位分组,collectGroup的key为position值,value为对象中position和key相同的集合
Map<String, List<Employee>> collectGroup = list.stream().collect(Collectors.groupingBy(employee -> employee.getPosition()));
System.out.println(JSON.toJSONString(collectGroup));

//()计算list的size,等价于().count()
Long collectCount = list.stream().collect(Collectors.counting());
//collectCount值为7
System.out.println(collectCount);

//()转换成set,舍弃重复值
Set<Employee> collectSet = list.stream().collect(Collectors.toSet());
System.out.println(JSON.toJSONString(collectSet));

//()根据传入的属性获取对应属性的key和value
Map<String, String> collectMap = list.stream().collect(Collectors.toMap(Employee::getName, Employee::getPosition));
System.out.println(JSON.toJSONString(collectMap));

//()计算list中属性为salary的平均值
Double collectDouble = list.stream().collect(Collectors.averagingDouble(Employee::getSalary));
//collectDouble=18214.745714285713
System.out.println(collectDouble);

//()计算list中属性为age的平均值
//averagingInt和averagingDouble差不多,不同的地方在于averagingInt在执行会把元素看成int,最后结果为Double,而averagingDouble在执行的时候会把元素看成Double,返回也是Double
Double collectInt = list.stream().collect(Collectors.averagingInt(Employee::getAge));
//collectInt=36.42857142857143
System.out.println(collectInt);

//()相当于().map(),收集name属性
//()每次收集一次name都在后面加上,最后一次没有
String collectMapping = list.stream().collect(Collectors.mapping(Employee::getName, Collectors.joining(",")));
//collectMapping=张三,李四,王五,麻六,孙七,赵八,钱九
System.out.println(collectMapping);

//()求最大值,相当于().max()
Employee employeeMax = list.stream().collect(Collectors.maxBy((a, b) -> a.getAge() - b.getAge())).get();
//employeeMax的age属性为56
System.out.println(employeeMax.getAge());

//()和()类似,相当于().min()
Employee employeeMin = list.stream().collect(Collectors.minBy((a, b) -> a.getAge() - b.getAge())).get();
//employeeMin的age属性为25
System.out.println(employeeMin.getAge());

//()和().reduce()方法类似
Employee employeeReducing = list.stream().collect(Collectors.reducing((a, b) -> {
return a.getSalary() > b.getSalary() ? a : b;
})).get();
//employeeReducing的salary属性为50000.42
System.out.println(employeeReducing.getSalary());

2.2.10、终端操作-foreach

//forEach()遍历list
list.stream().forEach(employee -> System.out.println(employee));

2.2.11、终端操作-findFirst、findAny、anyMatch、allMatch、noneMatch

//findFirst查找list第一个元素,相当于(0)
Employee employeeFindFirst = list.stream().findFirst().get();
System.out.println(employeeFindFirst);

//findAny查找任意一个,如果有多个只会匹配第一个
Employee employeeFindAny = list.stream().findAny().get();
System.out.println(employeeFindAny);

//anyMatch,只要有符合条件的就返回true,默认为false
boolean anyMatch = list.stream().anyMatch(employee -> 27 == employee.getAge());
//anyMatch=true
System.out.println(anyMatch);

//allMatch,必须都符合条件的才返回true,默认为true
boolean allMatch = list.stream().allMatch(employee -> 27 == employee.getAge());
//allMatch=false
System.out.println(allMatch);

//noneMatch,必须全部不匹配才返回true,默认为true
boolean noneMatch = list.stream().noneMatch(employee -> 27 == employee.getAge());
//noneMatch=false
System.out.println(noneMatch);

2.2.12、终端操作-reduce

//reduce规约计算,入参n个值,返回1个值
Double reduce = list.stream().map(Employee::getSalary).reduce((a, b) -> {
return a + b;
}).get();
//reduce=127503.22
System.out.println(reduce);

2.2.13、终端操作-max、count、min

//计算list的count,相当于()
long count = list.stream().count();
//count=7
System.out.println(count);

//计算list中相关值的最大值
Employee max = list.stream().max((a, b) -> a.getAge() - b.getAge()).get();
//max的age属性为56
System.out.println(max.getAge());

//计算list中相关值的最小值
Employee min = list.stream().min((a, b) -> a.getAge() - b.getAge()).get();
//min的age属性为25
System.out.println(min.getAge());

以上就是stream流中常用的一些api,至于stream流的高级用法及源码分析将放在后续的文章中讲解。