JAVA Stream 流

本文详细介绍了Java Stream的概念,特点以及工作流程。主要内容包括流的创建、转换和计算结果,涵盖并行流、过滤、排序、映射、聚合等操作。此外,还讨论了Stream相对于传统循环迭代的优势及使用注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言:

Stream定义:

sequence of elements from source that supports aggregate operations.

sequence of elements:一个流对外提供一个接口,可以访问到一串特定的数据。流不存储元素,但是可以根据需要进行计算转化

source:数据来源,如数据结构,数组,文件等

aggregate operation:聚合操作,流支持像SQL操作或者其他函数式语言的操作,如filter、map、reduce、find、match、sorted等

 

Stream流特点:

pipelining:很多流操作也是返回一个流,如.filter() 

Internal Iteration:流操作进行迭代,用户感知不到循环遍历。

 

流的工作流程:

流的创建

流的转换:将流转换为其他流的中间操作,可包括多个步骤(惰性操作)

流的计算结果:该操作会强制执行之前的惰性操作,也就是说只有第三步启动,才会执行第二部的惰性操作,此步骤之后,流就不可用了

一切流操作都逃不出流的三大工作流程!一个流只能被使用一次

 

一、stream流的创建——产生一个流对象

1.Collection接口的stream方法

        Stream<String> as = new ArrayList<String>().stream();
        Stream<String> hs = new HashSet<String>().stream();
        //还有LinkedList,linkedSet,TreeSet,Vector等等

2.Arrays.stream可以将数组转化为Stream

        Stream<String> stream = Arrays.stream("a,b,c,d,e".split(","), 3, 5);
        //数组第3到第五个元素放到流中去

3.利用Stream类进行转化

        //of方法,直接将数组转化
        Stream<Integer> c1 = Stream.of(new Integer[5]);
        Stream<String> c2 = Stream.of("a,b,c".split(","));
        Stream<String> c3 = Stream.of("a", "b", "c");

        //产生一个空流
        Stream<Object> d1 = Stream.empty();

        //generate方法,接收一个lambda表达式
        Stream<String> e1 = Stream.generate(() -> "hello");
        Stream<Double> e2 = Stream.generate(Math::random);

        //iterate方法,接受一个种子,和一个lambda表达式
         Stream<BigInteger> iterate = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));

4.基本类型流

IntStream、LongStream、DoubleStream(只有这三种)

        IntStream s1 = IntStream.of(1, 2, 3, 4, 5);
        s1 = Arrays.stream(new int[]{1,2,3});
        s1 = IntStream.generate(() -> (int) (Math.random() * 100));
        s1=IntStream.range(1,5);//1,2,3,4
        s1=IntStream.rangeClosed(1,5);//1,2,3,4,5
        IntStream s2 = IntStream.of(1, 2, 3, 4, 5);
        Stream<Integer> s3 = s2.boxed();//转换为对象流
        IntStream s4 = s3.mapToInt(Integer::intValue);//转换为IntStream

5.并行流——使得所有的中间转换操作都将被并行化

        Stream<String> stringStream = new ArrayList<String>().parallelStream();//分治操作
        IntStream intStream = IntStream.range(0, 10000000).parallel();
        long count = intStream.filter(n -> n % 2 == 0).count();
        System.out.println(count);
        //需要保证传给并行流的操作不存在竞争

6.其他类/方法产生Stream流

        Stream<String> contents = Files.lines(Paths.get("D:\\PATH\\项目报告.docx"));//New IO(NIO)
        Stream<String> words = Pattern.compile(",").splitAsStream("a,b,c");//将,编译成正则表达式

二、stream流的转换——从一种流到另外一种流

1.过滤,去重

        //        过滤
        //        filter(Predicate<? super T> Predicate)
        //        接受一个lambda表达式,对每个元素进行判定,符合条件的留下
        Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> s2 = s1.filter(n -> n > 2);
        s2.forEach(System.out::println);//3,4,5
        //distinct()
        Stream<Integer> s1 = Stream.of(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 7);
        Stream<Integer> s2 = s1.distinct();
        s2.forEach(System.out::println);//1,2,3,4,5,6,7

对象流去重,先调用hashcode方法,再调用equals方法,和hashset添加元素判断重复方式一样

        ArrayList<Student> students = new ArrayList<>();
        students.add(new Student("tom",20));
        students.add(new Student("tom",20));
        students.add(new Student("Jerry",20));
        students.add(new Student("Jerry",18));

        Stream<Student> s3 = students.stream().distinct();
        s3.forEach(n-> System.out.println(n.toString()));
        //Student{name='tom', age=20}
        //Student{name='Jerry', age=20}
        //Student{name='Jerry', age=18}
    @Override
    public boolean equals(Object o) {
        Student s=(Student) o;
        if ((this.age==s.age)&&this.name.equals(s.name)){
            return true;
        }else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return name.hashCode()*1000+age;
    }

2.排序

基本类型包装类排序

        //默认排序
        Stream<Integer> s1 = Stream.of(3, 2, 1, 4, 6, 5);
        Stream<Integer> s2 = s1.sorted();
        s2.forEach(System.out::print);//123456
        //自定义排序
        Stream.of(plannets).sorted(Comparator.comparing(String::length)).forEach(System.out::print);//Mars,Venus,Earth,Saturn,Uranus,Mercury,Jupiter,Neptune
        //Stream.of(plannets).sorted((n1,n2)->n1.length()-n2.length()).forEach(System.out::print);

对象排序

        ArrayList<Student> students = new ArrayList<>();
        students.add(new Student("tom",20));
        students.add(new Student("tom",20));
        students.add(new Student("Jerry",20));
        students.add(new Student("Jerry",18));
        Stream<Student> s7 = students.stream().sorted();//默认调用对象的compareTo()方法
        s7.forEach(n-> System.out.println(n.toString()));
        //Student{name='Jerry', age=18}
        //Student{name='tom', age=20}
        //Student{name='tom', age=20}
        //Student{name='Jerry', age=20}
    @Override
    public int compareTo(Student o) {
        return this.age-o.age;

    }

3.转化

1).map()

利用方法引用对流每个元素进行函数计算

        Stream<Double> s1 = Stream.of(-1.5, 2.5, -3.5);
        Stream<Double> s2 = s1.map(Math::abs);
        s2.forEach(System.out::println);//1.5,2.5,3.5

利用lambda表达式对流元素进行函数计算

        Stream<Integer> s3 = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> s4 = s3.map(n -> n * n);
        s4.forEach(System.out::println);//1,4,9,16,25

可以对每个元素计算并返回stream流

    public static Stream<String> letters(String word){
        ArrayList<String> result = new ArrayList<>();
        for(int i=0;i<word.length();i++){
            result.add(word.substring(i,i+1));
        }
        return result.stream();
    }
        Stream<Stream<String>> allLetters = Stream.of(plannets).map(n->letters(n));
        allLetters.forEach(n-> {
            n.forEach(System.out::print);//MercuryVenusEarthMarsJupiterSaturnUranusNeptune
        });
//        Stream<Stream<String>> allLetters = Stream.of(plannets).map(n->letters(n));
//        allLetters.forEach(n-> {
//            n.forEach(System.out::print);//MercuryVenusEarthMarsJupiterSaturnUranusNeptune
//        });//Stream流中的元素还是一个Stream流
        Stream<String> allLetters = Stream.of(plannets).flatMap(n -> letters(n));//将结果扁平化,流中元素是字符
        allLetters.forEach(System.out::println);//一次遍历就可得到,每个String字符

2)抽取、跳过、连接

        Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Stream<Integer> s2 = s1.limit(3);
        s2.forEach(System.out::println);//1,2,3
        Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Stream<Integer> s2 = s1.skip(8);
        s2.forEach(System.out::println);//9,10
        //连接两个流
        Stream<String> s1 = Stream.concat(letters("hello"), letters("world"));
        s1.forEach(System.out::print);//helloworld

4.其他...

        Stream<Integer> s1 = Stream.iterate(1, n -> n * 2);
        Stream<Integer> s2 = s1.peek(n -> System.out.println("number:" + n)).limit(5);//对流做一些额外操作
        s2.forEach(System.out::println);//number:1 1number:2 2number:4 4number:8 8number:16 16

三、流的计算结果——终止操作

在此之前,先介绍一下Optional类型=_=

        Integer[] a = {1, 2, 3, 4, 5, 6};
        Stream<Integer> s1 = Stream.of(a);
        Optional<Integer> maxResult = s1.max((n1, n2) -> n1 - n2);
        System.out.println("最大数:"+maxResult.get());//最大数:6
        Integer[] a = {1, 2, 3, 4, 5, 6};
        Stream<Integer> s1 = Stream.of(a);
        Optional<Integer> max = s1.filter(n -> n > 10).max((n1, n2) -> n1 - n2);
        System.out.println("最大数:"+max.get());//err:No value present

第二段代码报错没有值匹配是因为optional源码里面有个空值判断

Optional类型是什么呢?

为了解决Java流操作返回空指针异常的问题,在流运算中,避免对象是否为null的判定

封装了Optional<T>

包装器对象

要么包装了类型T的对象,要么没有包装任何对象

如果T是null,那么可以返回一个替代物

        Optional<String> s1=Optional.of("abc") ;
        String s = s1.get();
        System.out.println("s:"+s);//abc
        Optional<String> eo = Optional.empty();
        String def = eo.orElse("def");//Return the value if present, otherwise return {@code other}.
        System.out.println(def);//def

Optional对象的创建和常用方法

        Optional<String> s1 = Optional.of("abc");
        Optional<String> s2 = Optional.empty();
        String s3=null;
        Optional<String> s4=Optional.ofNullable(s3);//这里s3为空,等价于Optional.empty();
        //get()获取值,不安全
        //orElse(T v)获取值,如果值不存在,采用替代
        //orElseGet(supplier)获取值,如果值不存在,采用lambda表达式结果替代
        //orElseThrow()获取值,如果值不存在,抛出异常
        //ifPresent()判空,不空返回true
        //isPresent(Consumer)判空,不为空,进行consumer式操作
        //map(Function)将值传递给Function函数计算,为空不计算

注意:使用get时,容易引发NoSuchElementException异常;

           使用isPresent判空,和判断null一样低效。

 

1.简单约简(聚合函数):count、max、min/.......

        //count()计数
        //max(Comparator)最大值,需要比较器
        //min(Comparator)最小值,需要比较器
        //findFirst()找到第一个元素
        //findAny()找到任意一个元素
        //anyMatch(Predicate)任意元素满足Predicate,返回true
        //allMatch(Predicate)所有元素满足Predicate,返回true
        //noneMatch(Predicate)没有任何元素满足predicate,返回true

2.自定义约简:reduce

可以在标准函数库的操作没有的时候来自定义。

        Integer[] a = {2, 4, 6, 8, 10};
        Stream<Integer> s1 = Stream.of(a);
        Optional<Integer> sum = s1.reduce(Integer::sum);//传递一个二元函数
        System.out.println(sum.orElse(0));//30
        Integer[] a = {2, 4, 6, 8, 10};
        Stream<Integer> s1 = Stream.of(a);
        Optional<Integer> product = s1.reduce((x, y) -> x * y);//传递一个二元函数BinaryOperator
        System.out.println(product.get());//3840
        Integer[] a = {2, 4, 6, 8, 10};
        Stream<Integer> s1 = Stream.of(a);
        Integer product3 = s1.reduce(2, (x, y) -> x * y);//传递一个二元函数BinaryOperator
        System.out.println(product3);//7680

3.查看、遍历元素:iterator、forEach

        Integer[] a = {2, 4, 6, 8, 10};
        Stream<Integer> s1 = Stream.of(a);
        Iterator<Integer> it1 = s1.filter(n -> n > 2).iterator();
        while(it1.hasNext()){
            System.out.println(it1.next());
        }
        Stream<Integer> s2 = Stream.of(a);
        s2.filter(n->n>2).forEach(System.out::println);

4.存放到数据结构中

        Integer[] a = {2, 4, 6, 8, 10};
        Stream<Integer> s1 = Stream.of(a);
        List<Integer> collect = s1.collect(Collectors.toList());
        System.out.println(collect);
        //toArray(),将结果转为数组
        //collect(Collectors.toList())转为List
        //collect(Collectors.toSet())转为Set
        //collect(Collectors.toMap())转为Map
        //collect(Collectors.joining())将结果连接起来

Java Stream的优点

统一转换元素,无需之前那样遍历

过滤元素

单个操作合并元素

将元素序列存放到某一个集合中

.......

类似SQL操作”做什么而非怎么做“

简化了串行,并行的大批量操作

 

Java Stream 和循环迭代代码的区别

1.Stream广泛使用lambda表达式,只能读取外围的final或者effectively final变量,循环迭代代码可以读取、修改任意的局部变量

2.在循环迭代代码块中,可以随意break、continue、return。或者抛出异常,而lambda表达式无法完成这些事情。

注意事项:

1.一个流,一次只能一个用途,不能多个用途,用了不能再用(否则会流已经被操作或者被关闭报错)

2.避免创建无限流

3.注意转换的顺序

        skip().limit()

        limit().skip()

4.谨慎使用并行流

        底层使用Fork-join Pool,处理计算密集型任务

        数据量过小不用

        数据结构不容易分解不用,LinkedList等

        数据频繁拆箱装箱不用

        设计findFirst或者limit不用

5.Stream和Collection可以相互转化

        数据可能无限,用Stream

        数据很大很大,用Stream

        调用者使用查找、过滤、聚合操作,用Stream

        调用者使用过程中,发生数据改变,而调用者需要对数据一致性(实时性)有较高要求,用collection

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Pixie:)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值