Stream流

java8 新特性 Stream 流

本文主要参考的是书籍《java核心技术卷Ⅱ》

认识 Stream 流

Stream 流的概念

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输,并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作 (terminal operation)得到前面处理的结果。

为什么引入 Stream 流操作

​ 通过使用流操作可以更加的方便我们在业务操作中的使用。在这里我们不需要自己去写一些逻辑代码,我们可以说明想要完成什么任务,而不是说明如何去实现它们 ,这样就极大的方便了我们的实际开发。总结成一句话就是:我们只在乎结果不在乎实现的过程。

Stream 流与传统集合的差别
  1. 流并不存储数据,这些数据可能是存储在底层的集合中的也可能是根据函数生成的(这个后面会提到)。
  2. 流的操作不会修改数据源,例如:filter 方法只是生成一个新的流并不会对原有的数据产生影响。
  3. 流的操作尽可能使惰性的。这样意味着到需要结果时才会开始执行。

Stream 流的创建

  1. 如果使集合类可以使用 Collection 接口的 stream() 方法将集合转化成为一个流。例如:

    List<String> list = new ArrayList<>();
    Stream<String> stream = list.stream();
    
  2. 如果是数组可以使用静态方法 Stream.of() 和工具类 Arrays.stream() 方法

    int[] a = {1, 2, 3, 4, 5};
    IntStream stream = Arrays.stream(a);
    
    Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
    
  3. 使用 Stream 类提供的 generate(function) 方法, 中间的参数传递一个函数,Stream 会 无限 的调用这个方法进行生成,这样就会生成一个无限流,可以使用 limit() 方法进行限制。

    // 这里会生成一个 无限流
    Stream<String> generate = Stream.generate(() -> "ss");
    // 会无限输出 "ss"
    generate.forEach(System.out::println);
    

Stream 流的使用

遍历和查找

​ Stream 支持遍历和查找元素的操作,结果返回的使 Optional 的值。

方法作用
Optional max()查找最大值
Optional min()查找最小值
Optional findFirst()查找第一个符合条件的元素
Optional findAny()查找符合条件的任意一个元素
boolean anyMatch()只要符合条件就行
boolean allMatch()所有元素符合条件才行
boolean noneMatch()没有元素符合就行
List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> max = list1.stream().max(Comparator.comparingInt(x -> x));
System.out.println("最大值:" + max.get());
Integer first = list1.stream().filter(x -> x > 2).findFirst().get();
System.out.println("第一个值:" + first);
System.out.println("遍历");
list1.stream().forEach(System.out::print);

在这里插入图片描述

在 Stream 流中可以采用 iterator 和 forEach 方法进行遍历。

收集结果
方法作用
void forEach(action)在每一个元素上使用 action 方法
Object[] toArray()产生一个对象数组
toList(),toSet()转化成集合
String joining(value)生成一个 value 进行分割的字符串
summarizingInt/Double/Long获取最大值,最小值,平均值,总和,数量

​ 如果我们需要将 stream 流中的结果收集到数据结构(数组)中,那么我们可以使用 toArray() 方法但是会返回一个 Object类型的数组,这并不是我们需要的,我们可以手动指定返回类型。

List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
Object[] objects = list1.stream().toArray(); // 这里返回了一个 Object 类型的数组
// 指定类型
Integer[] integers = list1.stream().toArray(Integer[]::new);

​ 争对将 stream 流中的数据进行转化我们也许更喜欢转换成为集合相关的 java 容器。这里我们就可以使用一个便捷方法 collect,它接受一个 Collector 接口的实例。Collectors 类提供了大量用于生成收集器的方法(收集器:一种将众多元素产生为单一结果的对象)。可以使用方法 Collectors.toList() 和 Collectors.toSet() 方法生成 List 集合 和 Set 集合。如果想要生成具体的实现类可以采用 Collectors.toCollection() 方法,例如:Collectors.toCollection(HashSet::new) 生成 HashSet

List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> collect2 = list1.stream().collect(Collectors.toList());
Set<Integer> collect3 = list1.stream().collect(Collectors.toSet());
TreeSet<Integer> collect4 = list1.stream().collect(Collectors.toCollection(TreeSet::new));

如果需要在元素之间添加分隔符,可以使用 Collectors.joining() 方法。

String result  = stream.collect(Collectors.joining(", "))

如果流中包含除字符串以外的其他对象,那么我们需要先将其转换为字符串

String result = stream.map(Object::toString).collect(Collectors.joining(", "))

​ 如果想要将流的结果约简为总和、数量、平均值、最大值或最小值,可以使用summarizing(Int|Long|Double)方法中的某一个。这些方法会接受一个将流对象映射为数值的函数,产生类型为(Int|Long|Double)SummaryStatistics的结果,同时计算总和、数量、平均值、最大值和最小值。

IntSummaryStatistics collect = Stream.of(1, 2, 3, 4, 5).collect(Collectors.summarizingInt(s->s));
System.out.println("最大值:" + collect.getMax());
System.out.println("平均值:" + collect.getAverage());

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
在这里插入图片描述

收集到映射表中

​ 假设我们有一个 Stream,并且想要将其元素收集到一个映射表中,这样后续就可以通过它们的ID来查找人员了。Collectors.toMap 方法有两个函数引元,它们用来产生映射表的键和值。

employeeList.add(new Employee(1, "a", new int[]{1, 2, 3}));
employeeList.add(new Employee(2, "b", new int[]{4, 2, 3}));
employeeList.add(new Employee(3, "c", new int[]{5, 2, 3}));
employeeList.add(new Employee(4, "d", new int[]{8, 2, 3}));
employeeList.add(new Employee(5, "e", new int[]{9, 2, 3}));
Map<Integer, Employee> collect = employeeList.stream().collect(Collectors.toMap(Employee::getId, Employee::getName);
collect.forEach((k,v)-> System.out.println(k + "  " + v));

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传在这里插入图片描述

通常情况下,map 中的 value 应该使实际的值,因此第二个函数可以使用 Function.identity()。

Map<Integer, Employee> collect = employeeList.stream().collect(Collectors.toMap(Employee::getId, Function.identity()));
collect.forEach((k,v)-> System.out.println(k + "  " + v));

在这里插入图片描述

这里的 Function.identity(): 返回元素本身 源码中是 t -> T 这里的 t 就是类本身,这里的类为 Employee

​ 如果有多个元素具有相同的键,就会存在冲突,收集器将会抛出一个IllegalStateException异常。可以通过提供第3个函数引元来覆盖这种行为,该函数会针对给定的已有值和新值来解决冲突并确定键对应的值。这个函数应该返回已有值、新值或它们的组合。

// 添加一个新的 employee 到 list 集合中,这样 id=5 就重复了
employeeList.add(new Employee(5, "test", new int[]{9, 2, 3}));
Map<Integer, Employee> collect = employeeList.stream().collect(Collectors.toMap(Employee::getId, Function.identity(), (oldValue, newValue)->newValue));

在这里插入图片描述

这里的 5 已经进行了新值的覆盖,如果没有第三个函数的判断此时就会报错 因为 key 值重复了。

群组和分区

​ 在实际的开发过程中遇到将相同特性的值群聚成组是很常见的,则就可以使用 groupingBy() 方法。

Subject subject1 = new Subject(1, "语文", 91.0);
Subject subject2 = new Subject(2, "数学", 94.0);
Subject subject3 = new Subject(3, "英语", 92.0);
Subject subject4 = new Subject(4, "语文", 90.0);
List<Subject> subjectList = new ArrayList<>();
subjectList.add(subject1);
subjectList.add(subject2);
subjectList.add(subject3);
subjectList.add(subject4);
// 对学科的科目进行分组
Map<String, List<Subject>> collect4 = 
subjectList.stream().collect(Collectors.groupingBy(Subject::getName));
collect4.forEach((k,v)-> System.out.println(k + "  " + v));

在这里插入图片描述

当分类函数是断言函数(即返回boolean值的函数)时,流的元素可以分为两个列表:该函数返回 true 的元素和其他的元素。在这种情况下,使用 partitioningBy() 比使用 groupingBy() 更高效。

// Collectors.partitioningBy(s->...) 根据 s 中的表达式进行分组 表达式的返回值必须是 boolean 值 s 为当前的类
Map<Boolean, List<Subject>> collect1 = subjectList.stream().collect(Collectors.partitioningBy(s -> "语文".equals(s.getName())));
collect1.forEach((k,v)-> System.out.println(k + " " + v));

在这里插入图片描述

约简操作

​ reduce方法是一种用于从流中算某个值的通用机制,其最简单的形式将接受一个二元函数,并从前两个元素开始持续应用它。如果该函数是求和函数,那么就很容易解释这种机制。

Optional<Integer> reduce1 = Stream.of(1, 2, 3, 4, 5).reduce((x, y) -> {
            System.out.println(x + "  " + y);
            return x + y;
});

在这里插入图片描述

从结果我们可以得知:

在第一次执行的时候 x 为数组的第一个元素 y 为数组的第二个元素,在后面执行的时候 x 为前面结果的和, y 为下一个元素 => 此时的返回结果为 Optional 类

第二种形式,可以为 reduce() 函数添加一个起点值。

Integer reduce = Stream.of(1, 2, 3, 4, 5).reduce(0, (x, y) -> {
            System.out.println(x + " " + y);
            return x + y;
        });

从结果我们可以得知:

如果在前面添加起始点 0,在第一次执行的时候 x 为初始值0, y 为数组的第一个元素。在后面执行的时候,,x 为前面结果的和, y 为下一个元素 => 此时的结果为 基础类型 因为就算流中没有元素也有初始值 0

基本类型流

​ 到目前为止,我们都是将整数收集到Streams中,尽管很明显,但是将每个整数都包装到包装器对象中却是很低效的。对其他基本类型来说,情况也是一样,这些基本类型是double、float、long、short、char、byte和boolean。流库中具有专门的类型IntStream、LongStream和DoubleStream,用来直接存储基本类型值,而无须使用包装器。如果想要存储short、char、byte和boolean,可以使用IntStream;而对于float,可以使用DoubleStream。

创建基本类型流

为了创建IntStream,需要调用IntStream.of() 和 Arrays.stream() 方法:

IntStream intStream = IntStream.of(1, 2, 3, 4, 5);

有可能在实际的业务中,我们的流中的数据使对象,这时我们就可以使用 mapToInt、mapToLong 或 mapToDouble 将其转换为基本类型流。

Stream<String> stream = Stream.of("qqq", "china", "American");
IntStream intStream = stream.mapToInt(String::length);

为了将基本类型流转换为对象流,需要使用boxed方法:

IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
Stream<Integer> boxed = intStream.boxed();

通常,基本类型流上的方法与对象流上的方法类似。下面是主要的差异:

  1. toArray方法会返回基本类型数组。
  2. 产生可选结果的方法会返回一个OptionalInt、OptionalLong或OptionalDouble。这些类与Optional类类似,但是具有getAsInt、getAsLong和getAsDouble方法,而不是get方法。
  3. 具有分别返回总和、平均值、最大值和最小值的sum、average、max和min方法。对象流没有定义这些方法。
  4. summaryStatistics方法会产生一个类型为IntSummaryStatistics、LongSummaryStatistics或DoubleSummaryStatistics的对象,它们可以同时报告流的总和、数量、平均值、最大值
    和最小值。
    le。这些类与Optional类类似,但是具有getAsInt、getAsLong和getAsDouble方法,而不是get方法。
  5. 具有分别返回总和、平均值、最大值和最小值的sum、average、max和min方法。对象流没有定义这些方法。
  6. summaryStatistics方法会产生一个类型为IntSummaryStatistics、LongSummaryStatistics或DoubleSummaryStatistics的对象,它们可以同时报告流的总和、数量、平均值、最大值
    和最小值。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值