首页 > 编程语言 >关于java中Stream理解

关于java中Stream理解

时间:2024-08-27 17:06:12浏览次数:8  
标签:java name Stream height 理解 Student sex true id

关于java中Stream理解

Stream是什么

Stream:Java 8新增的接口,Stream可以认为是一个高级版本的Iterator。它代表着数据流,流中的数据元素的数量可以是有限的,
也可以是无限的。

Stream跟Iterator的差别是

无存储:流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。

函数式编程:对数据流的任何修改都不会修改背后的数据源,比如对流执行滤波器操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新的流。

惰性执行:Stream的操作由零个或多个中间操作(中间操作)和一个结束操作(terminal operation)两部分组成。只有执行了结束操作,Stream定义的中间操作才会依次执行,这就是Stream延迟特性。从后往前追溯,直到起始。

惰性求值(lazy evaluation):惰性求值是一种将对函数或请求的处理延迟到真正需要结果时进行求值的方式。

可消费性:流只能被“消费”一次,一旦遍历过就会失效就像容器的迭代器那样,想要再次遍历必须重新生成一个新的数据流。

对stream的操作分为为两类,二者特点是:

中间操作(intermediate operations) 中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream,仅此而已。

结束操作(terminal operations) 结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。

为什么要使用stream

代码简洁,函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环。

多核友好,Java函数式编程使得编写并行程序从未如此简单,你需要的全部就是调用一下parallel()方法。

源码解析与使用示例

1.如何创建Stream

  • Collection.stream()或者Collection.parallelStream()

  • 调用Arrays.stream(T[] array)方法。

  • 使用Stream.builder()使用.add(x)添加元素使用build()完成构建

  • Stream.of来创建

2.操作方式

以下是部分常用操作方式

操作类型接口方法
中间操作 caoncat() distinct() limit() peek() skip() sorted() filter()
parallel() sequential() unordered() map() flatMap()
结束操作 forEach() forEachOrdered() toArray() findAny() findFirst()
allMatch() anyMatch() noneMatch() reduce() max() min() count() collect()

通常情况下Stream和函数接口关系非常紧密,没有函数接口steam就无法工作。而函数接口出现的地方都可以使用lambda表达式.

中间操作

在讲解操作之前先构造一些基础的实体类和处理逻辑

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Student {

    private Integer id;

    private String name;

    private Boolean sex;

    private Double height;

}

//原始数据
private static List<Student> getWList() {
     Student s7 = new Student(7,"貂蝉",false,1.65);
     Student s8 = new Student(8,"小乔",false,1.61);
     Student s9 = new Student(8,"小乔",false,1.61);
     List<Student> wList = new ArrayList<>();
     wList.add(s7);
     wList.add(s8);
     wList.add(s9);
     return wList;
 }

 private static List<Student> getMList() {
     Student s1 = new Student(1,"刘备",true,1.72);
     Student s2 = new Student(2,"关羽",true,1.80);
     Student s3 = new Student(3,"张飞",true,1.82);
     Student s4 = new Student(4,"黄忠",true,1.81);
     Student s5 = new Student(5,"马超",true,1.70);
     Student s6 = new Student(6,"赵云",true,1.75);
     List<Student> mList = new ArrayList<>();
     mList.add(s1);
     mList.add(s2);
     mList.add(s3);
     mList.add(s4);
     mList.add(s5);
     mList.add(s6);
     return mList;
 }

 

每次调用方法都会创建新的list对象,不会出现list耗尽的情况

在学习方法之前有一个新的操作符需要理解

:: 操作符: 表示调用该对象的某方法 Student::getHeight 等同于 x->x.getgetHeight() 等同于 (x) -> {return x.getHeight()} System.out::println
等同于x -> System.out.println(x)

什么是双冒号操作符

1. 连接 caoncat()

创建一个延迟的连接流
接口

public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
   Objects.requireNonNull(a);
   Objects.requireNonNull(b);

   @SuppressWarnings("unchecked")
   Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
           (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
   Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
   return stream.onClose(Streams.composedClose(a, b));
}

 

操作过程

// 合并 concat()
Stream<Student> stream = Stream.concat(mList.stream(), wList.stream());
// 结束操作
streamSorted.forEach(System.out::println);

 

打印结果:
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=3, name=张飞, sex=true, height=1.82)
Student(id=4, name=黄忠, sex=true, height=1.81)
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=6, name=赵云, sex=true, height=1.75)
Student(id=7, name=貂蝉, sex=false, height=1.65)
Student(id=8, name=小乔, sex=false, height=1.61)
Student(id=8, name=小乔, sex=false, height=1.61)

打印过结果显示是该流被整合在了一起

2. 去重 distinct()

除去重复元素,如果是对比对象内容需要重写equals方法
接口

Stream<T> distinct();

具体实现

// 去重 distinct()
Stream<Student> streamDistinct = wList.stream().distinct();
streamSorted.forEach(System.out::println);

打印结果:
Student(id=7, name=貂蝉, sex=false, height=1.65)
Student(id=8, name=小乔, sex=false, height=1.61)

可以看出多余的小乔被移除了

3.截断 limit()

截断流,使其元素不超过给定数量,可以理解为for循环执行到一定数量以后break。
接口

Stream<T> limit(long maxSize);

具体实现

// 截断 limit()
Stream<Student> streamLimit = mList.stream().limit(3);
streamSorted.forEach(System.out::println);

打印结果
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=3, name=张飞, sex=true, height=1.82)

可以看出流满足三个元素以后就被截断了。

4.跳过 skip()

跳过几个元素,返回一个丢掉了前n个元素的流,若元素不足n个则返回一个空的流。与limit()互补。
接口

Stream<T> skip(long n);

具体实现

// 跳过 skip
Stream<Student> streamSkip = mList.stream().skip(3);
streamSkip.forEach(x -> System.out.println(x.toString()));

打印结果
Student(id=4, name=黄忠, sex=true, height=1.81)
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=6, name=赵云, sex=true, height=1.75)

可以看出前三个元素被丢掉了

limit方法时从开始到limit的参数位置个元素留下其他的丢掉。skip方法时从开始到skip的参数个元素丢掉其他的留下。

5.排序 sorted

将元素按照某种既定的顺序排序
接口

// 默认比较器 值可排序的情况下
Stream<T> sorted();
// 比较器排序 自定义比较器
Stream<T> sorted(Comparator<? super T> comparator);  

实现方式 应为student是对象无法使用默认比较器。

System.out.println("排序 sorted方法");

System.out.println("排序前 stream");
Stream<Student> streamSorted = getMList().stream();
streamSorted.forEach(System.out::println);

// 第一种自定义比较方法
System.out.println("排序后 stream");
streamSorted = getMList().stream().sorted((x,y)->{
    if(x.getHeight().equals(y.getHeight())){
        return x.getName().compareTo(y.getName());
    }else{
        return x.getHeight().compareTo(y.getHeight());
    }
});
streamSorted.forEach(System.out::println);
System.out.println("排序后 stream");
// 第二种自定义比较方法
streamSorted = getMList().stream().sorted(Comparator.comparingDouble(Student::getHeight));
streamSorted.forEach(System.out::println);

打印结果
排序 sorted方法
排序前 stream
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=3, name=张飞, sex=true, height=1.82)
Student(id=4, name=黄忠, sex=true, height=1.81)
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=6, name=赵云, sex=true, height=1.75)
排序后 stream
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=6, name=赵云, sex=true, height=1.75)
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=4, name=黄忠, sex=true, height=1.81)
Student(id=3, name=张飞, sex=true, height=1.82)

6. 转化为并行流 parallel()

将程序并行执行,执行顺序并不是有序的。
接口

//该方法时存在于BaseStream
S parallel();

具体实现

Stream<Student> streamParallel = getMList().stream();
streamParallel.parallel().forEach(System.out::println);

打印结果
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=3, name=张飞, sex=true, height=1.82)
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=6, name=赵云, sex=true, height=1.75)
Student(id=4, name=黄忠, sex=true, height=1.81)

可以看出以上执行顺序已经乱了。

7. 转化为顺序执行 sequential()

将流转换为顺序执行
接口

 S sequential();

使用方式和 parallel类似,通常情况下流都是顺序执行的。

8. 标记无序化 unordered()

将这个流标记为无序,但是不会打乱其顺序。
接口

S unordered();
9.摊平 flatMap()

将一个一个list摊平为Student流
接口

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

实现方式

log.info("摊平 flatMap");
Stream<List<Student>> streamFlatMap1 = Stream.of(getWList(),getMList());
streamFlatMap1.forEach(System.out::println);
Stream<List<Student>> streamFlatMap = Stream.of(getWList(),getMList());
streamFlatMap.flatMap(Collection::stream).forEach(System.out::println);

打印结果

*[Student(id=7, name=貂蝉, sex=false, height=1.65),
Student(id=8, name=小乔, sex=false, height=1.61),
Student(id=8, name=小乔, sex=false, height=1.61)]

[Student(id=1, name=刘备, sex=true, height=1.72),
Student(id=2, name=关羽, sex=true, height=1.8),
Student(id=3, name=张飞, sex=true, height=1.82),
Student(id=4, name=黄忠, sex=true, height=1.81),
Student(id=5, name=马超, sex=true, height=1.7),
Student(id=6, name=赵云, sex=true, height=1.75)]

Student(id=7, name=貂蝉, sex=false, height=1.65)
Student(id=8, name=小乔, sex=false, height=1.61)
Student(id=8, name=小乔, sex=false, height=1.61)
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=3, name=张飞, sex=true, height=1.82)
Student(id=4, name=黄忠, sex=true, height=1.81)
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=6, name=赵云, sex=true, height=1.75)*

10. 转换映射 map()

就是对每个元素按照某种操作进行转换,转换前后Stream中元素的个数不会改变,但元素的类型取决于转换之后的类型。
接口

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

具体实现

log.info("转换映射 map");
Stream<Student> streamMap = getMList().stream();
//这里由一个Student流转换为一个double流
streamMap.map(Student::getHeight)
        .peek(x-> System.out.println(x.getClass().getName()))
        .forEach(System.out::println);

 

打印结果
09:52:08.665 [main] INFO stream.StreamApiExample - 转换映射 map
java.lang.Double
1.72
java.lang.Double
1.8
java.lang.Double
1.82
java.lang.Double
1.81
java.lang.Double
1.7
java.lang.Double
1.75

可以看出该流已经由Student流转换为一个double流了

结束操作

中间操作讲解完成了,其他的部分后续会补充。其实在讲解中间流的时候已经使用到一种结束操作,这就是遍历操作。

1.遍历操作 forEach() forEachOrdered()

如果流具有已定义的相遇顺序,则按流的相遇顺序对此流的每个元素执行操作。
主要区别在并行处理上,forEach方法不能保证在并行状态下流的执行是按照顺序出现的。而forEachOrdered()可以保证
接口

 void forEachOrdered(Consumer<? super T> action);
  void forEach(Consumer<? super T> action);

具体实现

IntStream.range(1,5).parallel().forEachOrdered(System.out::println);
IntStream.range(1,5).parallel().forEach(System.out::println);

打印结果
*1
2
3
4

1
2
4
3*

可以看到顺序被打乱了

注意:只有在并行状态下才会有区别。

2. 转换为数组 toArray()

将流对象转换为数组

Object[] toArray();

实现方式

Stream<Student> streamToArray = getMList().stream();
Object[] arr = streamToArray.toArray();
System.out.println(Arrays.toString(arr));

打印结果
[Student(id=1, name=刘备, sex=true, height=1.72),
Student(id=2, name=关羽, sex=true, height=1.8),
Student(id=3, name=张飞, sex=true, height=1.82),
Student(id=4, name=黄忠, sex=true, height=1.81),
Student(id=5, name=马超, sex=true, height=1.7),
Student(id=6, name=赵云, sex=true, height=1.75)]

3. 发现元素 findFirst() findAny()

在顺序的情况两个方法都是下返回第一个元素
在乱序的情况下 findFirst()返回的永远是顺序状态下的第一个元素。
findAny()返回的是第一个执行完的元素。
接口

Optional<T> findFirst();
Optional<T> findAny();

实现方式

log.info(" findFirst()和findAny");
Stream<Student> streamFindFirst = getMList().stream();
Optional<Student> stu = streamFindFirst.parallel().findFirst();
System.out.println(stu.get());
Stream<Student> streamFindAny = getMList().stream();
Optional<Student> stuAny = streamFindAny.parallel().findAny();
System.out.println(stuAny.get());

打印结果
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=2, name=关羽, sex=true, height=1.8)

4.条件全匹配 allMatch()noneMatch() anyMatch()

allMatch() 所有元素是否匹配该条件
noneMatch() 没有一个符合匹配条件
anyMatch() 至少有一个符合匹配条件

接口

//方法api
boolean test(T t);

boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);

实现方式

log.info("allMatch()");
Stream<Student> streamAllMatch = getMList().stream();
boolean res = streamAllMatch.allMatch(x -> x.getId() < 100);
System.out.println(res);

打印结果
true

注意:如果流是空的任意匹配都返回true 空虚真理

5. 聚合操作 reduce() sum()、max()、min()、count()
reduce() 自定义聚合操作
max() 求最大
min() 求最小
count() 统计数量

接口

// 元素如何合并
Optional<T> reduce(BinaryOperator<T> accumulator);
// 顺序执行时 初始值 identity 元素如何合并操作 accumulator
T reduce(T identity, BinaryOperator<T> accumulator);
// 在并行执行时
<U> U reduce(U identity,//初始值
            BiFunction<U, ? super T, U> accumulator, //如何合并成一部分
            BinaryOperator<U> combiner); //多部分如何合并
// 求最大 传入比较器
Optional<T> max(Comparator<? super T> comparator);
// 求最小 传入比较器
Optional<T> min(Comparator<? super T> comparator);
//统计元素个数
long count();

使用示例1

log.info("reduce() 一般都和map配合使用");
log.info("统计身高平均值");
Stream<Student> streamReduce = getMList().stream();
long count = streamReduce.count();
Optional<Double> total = getMList().stream().map(Student::getHeight)
        .reduce((a, b) -> a + b);
System.out.println(total.get()/count);

结果
1.7666666666666666

5.collect()

该方法可以解决大多数啊上面api解决不了的问题。
接口

<R> R collect(Supplier<R> supplier,
      BiConsumer<R, ? super T> accumulator,
      BiConsumer<R, R> combiner);

<R, A> R collect(Collector<? super T, A, R> collector);

使用示例:
将student对象中的name作为key height作为value生成一个Map

Stream<Student> streamollect = getMList().stream();
Map<String,String> s = streamReduce.collect(HashMap::new,
       (map,stu)-> map.put(stu.getName(),stu.getHeight()+""),
       HashMap::putAll);
System.out.println(s.toString());

结果:
{关羽=1.8, 张飞=1.82, 刘备=1.72, 马超=1.7, 赵云=1.75, 黄忠=1.81}

将流对象中的名称取出形成一个列表

Stream<Student> streamCollect = getMList().stream();
ArrayList<Object> list = streamCollect.collect(
        ArrayList::new,
        (arr, stu) -> arr.add(stu.getName()),
        ArrayList::addAll);
System.out.println(list.toString());

结果:
[刘备, 关羽, 张飞, 黄忠, 马超, 赵云]

执行过程

总结

collect的用法还有很多很多。从根本上将Stream是解决for循环不能并行优化的问题.而在这其中很多的lambda的方法都很违反常规的java写法。不过随着lambda的加入将会有越来越多的函数式编程方式来实现java的功能。

标签:java,name,Stream,height,理解,Student,sex,true,id
From: https://www.cnblogs.com/HePandeFeng/p/18383126

相关文章

  • Android开发 - Application 基础类全局的应用级状态管理解析
    Application是什么Application是一个基础类,用于全局的应用级状态管理。它在应用程序启动时被创建,并在应用程序关闭时销毁。Application对象的生命周期与应用程序的生命周期一致,因此它非常适合用来保存全局的应用状态信息或初始化全局资源Application的主要作用全局状态管......
  • JavaScript 对象构造器
    <!DOCTYPEhtml><htmllang="en"><body><pid="demo"></p><script>functionPerson(first,last,age,like){this.firstName=first;this.lastName=last;......
  • gstreamer教程(1)——gstreamer介绍
    介绍:GStreamer是一个用于创建流媒体应用程序的框架。基本设计来自OregonGraduateInstitute的video的管道(pipeline)以及DirectShow的一些想法和理念。GStreamer的开发框架可以编写任何类型的流式多媒体应用程序。GStreamer框架旨在使编写处理音频和/或视频的应用......
  • Java AOT思想
    AOT(Ahead-Of-Time)编译是一种编译技术,它可以在程序运行之前将源代码或字节码编译成机器代码,从而提高程序的启动速度和整体性能。在Java中,AOT机制能够有效地优化Java应用的启动时间,尤其是在需要快速响应的场景中,如微服务、容器化应用等。AOT编译的工作原理在传统的JVM(JavaVirt......
  • JAVA学习之集合
    1.集合的概念    将若干用途、性质相同或相近的“数据”组合而成的一个整体。    Java集合只能保存引用类型的数据,不能保存基本类型数据。Java常用集合:Set(集):集合中的对象不按特定方式排序,并且没有重复对象。List(列表):集合中的对象按照索引位置排序,可以有重......
  • java实现线性反馈移位寄存器实例
    题目:3级线性反馈移位寄存器C3=1时可有4种线性反馈函数,设其初始状态为(a1,a2,a3)=(1,0,1),输出由它们得到的密钥流,并分别利用生成的密钥流对明文“0x0123456789ABCDEF”进行加密,输出加密后的结果,再对密文进行解密,输出解密后的结果。1.分析相关题目详解:3级线性反馈移位寄存器......
  • (javaweb)事务管理+AOP
    目录1.spring事务管理2.rollbackFor(异常回滚属性)3.propagation(事物传播行为)AOP基础1.AOP概述AOP快速入门AOP核心概念APO进阶1.通知类型2.通知顺序3.切入点表达式 4.连接点5.AOP案例1.spring事务管理spring的第二大核心:AOP(面向切面编程)IOC是第一大核心:控制......
  • 网站提示400 - 请求错误,服务器无法理解客户端的请求怎么办
    当网站提示 400BadRequest 错误时,这意味着服务器无法理解客户端发送的请求。这种错误通常是由于客户端请求的格式有问题或者包含了一些服务器无法处理的信息。以下是解决 400BadRequest 错误的一些常见方法:常见原因URL输入错误:URL中可能存在语法错误或无效的参数。H......
  • 【java计算机毕设】网上商城MySQL springcloud vue HTML maven项目设计源码带项目报告
    目录1项目功能2项目介绍3项目地址 1项目功能【java计算机毕设】网上商城MySQLspringcloudvueHTMLmaven项目设计源码带项目报告PPT前后端可分离也可不分离 2项目介绍系统功能:网上商城包括管理员、用户两种角色。管理员功能包括个人中心模块用于修改个人......
  • JavaScript简介
    一、JavaScript简介1.什么是JavaScript?JavaScript简称为JS,由网景公司开发的客户端脚本语言,不需要编译,可以直接运行Web前端三层:结构层 HTML 定义页面的结构样式层 CSS 定义页面的样式行为层 JavaScript 用来实现交互,提升用户体验2.JavaScript作用在客户端动......