【java】Stream流

本文详细介绍了Java 8的Stream API,包括创建流、中间操作(如过滤、映射、排序)和终端操作(如收集、计数、查找与匹配)。讲解了如何通过Stream进行数据处理,例如筛选、映射转换、排序、去重、分组、规约等,并展示了各种实用示例,帮助理解Stream API的高效和便捷。
摘要由CSDN通过智能技术生成

什么是 Stream?

Stream 就好像一个高级的迭代器,但只能遍历一次,在流的过程中,对流中的元素执行一些操作,比如“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等。

要想操作流,首先需要有一个数据源,可以是数组或者集合。每次操作都会返回一个新的流对象,方便进行链式操作,但原有的流对象会保持不变。

流的操作可以分为两种类型:

  1. 中间操作,可以有多个,每次返回一个新的流,可进行链式操作。
  2. 终端操作,只能有一个,每次执行完,这个流也就用光光了,无法执行下一个操作,因此只能放在最后。

中间操作不会立即执行,只有等到终端操作的时候,流才开始真正地遍历,用于映射、过滤等。通俗点说,就是一次遍历执行多个操作,性能就大大提高了。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

以上的流程转换为 Java 代码为:

List<Integer> transactionsIds = 
widgets.stream()
             .filter(b -> b.getColor() == RED)
             .sorted((x,y) -> x.getWeight() - y.getWeight())
             .mapToInt(Widget::getWeight)
             .sum();

案例演示

1、创建流

package demo;

import org.junit.Test;
import java.util.stream.Stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Stream的3个步骤
 * 1、创建stream
 * 2、中间操作
 * 3、终止操作(终端操作)
 */
public class StreamDemo {

    //创建stream
    @Test
    public void test1(){
        //1、可以通过collection系列集合提供的串行流stream()或并行流parallelStream()
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();

        //2、通过Arrays中的静态方法stream()获取数组流
        Employee[] emps = new Employee[10];
        Stream<Employee> stream2 = Arrays.stream(emps);

        //3、通过stream类中的静态方法of()
        Stream<String> stream3 = Stream.of("a", "b", "c");

        //4、创建无限流
        //4.1迭代
        Stream<Integer> stream4 = Stream.iterate(0,(x)-> x+2);
        //stream4.forEach(System.out::println);
        stream4.limit(10).forEach(System.out::println);

        //4.2生成
        Stream.generate(()->Math.random())
                .limit(5)
                .forEach(System.out::println);
    }
}

查看 Stream 源码的话,你会发现 of() 方法内部其实调用了 Arrays.stream() 方法。

public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}

另外,集合还可以调用 parallelStream() 方法创建并发流,默认使用的是 ForkJoinPool.commonPool() 线程池。

List<Long> aList = new ArrayList<>();
Stream<Long> parallelStream = aList.parallelStream();

创建Employee类

public class Employee {
    private String name;
    private Integer age;
    private Double salary;
	//无参构造、有参构造、set、get、tostring()、hashcode、equals

2操作流

Stream 类提供了很多有用的操作流的方法

1)筛选与切片

filter:过滤流中的某些元素
limit(n):获取n个元素
skip(n):跳过n元素,配合limit(n)可实现分页
distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素

package demo;

import org.junit.Test;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;

/**
 * Stream的3个步骤
 * 1、创建stream
 * 2、中间操作
 * 3、终止操作(终端操作)
 */
public class StreamDemo2 {

    List<Employee> employees = Arrays.asList(
            new Employee("zhangsam",10,100.12),
            new Employee("lisi",20,200.12),
            new Employee("wangwu",30,300.12),
            new Employee("zhangyuting",40,500.12),
            new Employee("tangerrhu",450,500.12),
            new Employee("tangerrhu",450,500.12)
    );

    //中间操作
    /**
     * 筛选与切片
     * filter-接收lambda,从流中排除某些元素
     * limit—截断流,使其元素不超过给定数量
     * skip(n)—跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流。与limit(n)互补
     * distinct—筛选,通过流所生成元素的hashCode()和equals()去除重复元素
     */
    //内部迭代:迭代操作由Stream api完成
    @Test
    public void test1(){
        //中间操作:不会执行任何操作
        Stream<Employee> stream = employees.stream()
                .filter((e) -> {
                    System.out.println("Stream api 的中间操作");
                    return e.getAge()>30;
                });


        //终止操作:一次性执行全部内容,即"惰性求值”
        stream.forEach(System.out::println);
    }

    //外部迭代:迭代操作由自己完成,自己创建迭代器
    @Test
    public void test2(){
        Iterator<Employee> iterator = employees.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

    //limit
    @Test
    public void test3(){
        //先过滤再获取
        employees.stream()
                .filter((e) -> {
                    System.out.println("短路");
                    return e.getAge() > 10;
                })
                .limit(3)
                .forEach(System.out::println);
    }

    //skip,没跳过之前符合条件是4个人,跳过前2个之后,符合条件的只剩下后2个人
    @Test
    public void test4(){
        //先过滤再获取
        employees.stream()
                .filter((e) -> e.getAge() > 10)
                .skip(2)
                .forEach(System.out::println);
    }

    //distinct,要想去重成功,必须在泛型类Employee对象中重写hashcode和euqals才可以
    @Test
    public void test5(){
        //先过滤再获取
        employees.stream()
                .filter((e) -> e.getAge() > 10)
                .distinct()
                .forEach(System.out::println);
    }
}

filter() 方法接收的是一个 Predicate(Java 8 新增的一个函数式接口,接受一个输入参数返回一个布尔值结果)类型的参数,因此,我们可以直接将一个 Lambda 表达式传递给该方法,比如说 element -> element.contains(“皮”) 就是筛选出带有“皮”的字符串。

forEach() 方法接收的是一个 Consumer(Java 8 新增的一个函数式接口,接受一个输入参数并且无返回的操作)类型的参数,类名 :: 方法名是 Java 8 引入的新语法,System.out 返回 PrintStream 类,println 方法你应该知道是打印的。

stream.forEach(System.out::println); 相当于在 for 循环中打印,类似于下面的代码:

for (String s : strs) {
    System.out.println(s);
}

很明显,一行代码看起来更简洁一些。

2)映射

map 方法用于映射每个元素到对应的结果

map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

package demo;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * Stream的3个步骤
 * 1、创建stream
 * 2、中间操作
 * 3、终止操作(终端操作)
 */
public class StreamDemo3 {

    List<Employee> employees = Arrays.asList(
            new Employee("zhangsam",10,100.12),
            new Employee("lisi",20,200.12),
            new Employee("wangwu",30,300.12),
            new Employee("zhangyuting",40,500.12),
            new Employee("tangerrhu",450,500.12),
            new Employee("tangerrhu",450,500.12)
    );

    /**
     * 映射
     * map—接收lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
     * flatMap—接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
     */
    @Test
    public void test1(){
        List<String> list = Arrays.asList("aaa", "bbb","ccc", "ddd", "eee");
        list.stream()
                .map((e)->e.toUpperCase())
                .forEach(System.out::println);
    }

    @Test
    public void test2(){
        //method reference,Employee::getAge 和 (e)->e.getAge() 一样
        employees.stream()
                .map(Employee::getAge)
//                .map((e)->e.getAge())
                .forEach(System.out::println);
    }

    //遍历集合中的全部元素
    @Test
    public void test3(){
        List<String> list = Arrays.asList("aaa", "bbb","ccc", "ddd", "eee");
        Stream<Stream<Character>> stream = list.stream()
                .map(StreamDemo3::filterCharacter);

        stream.forEach((sm) -> {
            sm.forEach(System.out::println);
        });
    }

    public static Stream<Character> filterCharacter(String str){
        List<Character> list = new ArrayList<>();
        //字符串转变为字符数组,并进行遍历
        for (Character ch : str.toCharArray()){
            //将遍历后的字符放入list列表中
            list.add(ch);
        }
        return list.stream();
    }

    //flatMap,简化案例3
    @Test
    public void test4(){
        List<String> list = Arrays.asList("aaa", "bbb","ccc", "ddd", "eee");
        Stream<Character> characterStream = list.stream()
                .flatMap(StreamDemo3::filterCharacter1);

        characterStream.forEach(System.out::println);
    }

    public static Stream<Character> filterCharacter1(String str){
        List<Character> list = new ArrayList<>();
        //字符串转变为字符数组,并进行遍历
        for (Character ch : str.toCharArray()){
            //将遍历后的字符放入list列表中
            list.add(ch);
        }
        return list.stream();
    }

    /**
     * map和flatmap区别
     * map:将另一个集合整个添加到这个集合中
     * flatMap:将另一个集合中的每一个元素分别添加到这个集合中
     *
     * 我们用add和addAll来演示
     */
    //flatMap,简化案例3
    @Test
    public void test5(){
        List<String> list1 = Arrays.asList("aaa", "bbb","ccc", "ddd", "eee");
        List list2 = new ArrayList();
        list2.add(11);
        list2.add(22);

        list2.add(list1);
        System.out.println(list2);
        
        //结果:[11, 22, [aaa, bbb, ccc, ddd, eee]]

//        list2.addAll(list1);
//        System.out.println(list2);

        //结果:[11, 22, aaa, bbb, ccc, ddd, eee]
    }
}

map() 方法接收的是一个 Function(Java 8 新增的一个函数式接口,接受一个输入参数 T,返回一个结果 R)类型的参数,此时参数 为 String 类的 length 方法,也就是把 Stream 的流转成一个 Stream 的流。

3)排序

sorted 方法用于对流进行排序

sorted():自然排序,流中元素需实现Comparable接口
sorted(Comparator com):定制排序,自定义Comparator排序器

package demo;

import org.junit.Test;

import java.util.Arrays;
import java.util.List;

/**
 * Stream的3个步骤
 * 1、创建stream
 * 2、中间操作
 * 3、终止操作(终端操作)
 */
public class StreamDemo4 {

    List<Employee> employees = Arrays.asList(
            new Employee("zhangsam",10,100.12),
            new Employee("lisi",20,200.12),
            new Employee("wangwu",30,300.12),
            new Employee("zhangyuting",40,500.12),
            new Employee("tangerrhu",450,500.12),
            new Employee("tangerrhu",45,500.12)
    );

    /**
     * 排序
     * sorted()—自然排序(Comparable)
     * sorted(Comparator com)—定制排序(Comparator)
     */
    //自然排序
    @Test
    public void test1(){
        List<String> list = Arrays.asList("cc", "bb", "ee", "dd", "aa");
        list.stream()
                .sorted()
//                .sorted(String::compareTo)
                .forEach(System.out::println);
    }
	
	//定制排序
    @Test
    public void test2(){
        employees.stream()
                .sorted((e1,e2) -> {
                    if (e1.getAge().equals(e2.getAge())){
                        return e1.getName().compareTo(e2.getName());
                    }else {
                        return e1.getAge().compareTo(e2.getAge());
                    }
                })
                .forEach(System.out::println);
    }
}

4)查找与匹配

修改Employee类

public class Employee {
    private String name;
    private Integer age;
    private Double salary;
    private Status status;
//无参构造、有参构造、set、get、toString、hashcode、equals
	
	//创建枚举类
	public enum Status{
            FREE,
            BUSY,
            VOCATION
    }

举个栗子

Stream 类提供了三个方法可供进行元素匹配,它们分别是:

  • anyMatch(),只要有一个元素匹配传入的条件,就返回 true。
  • allMatch(),只有有一个元素不匹配传入的条件,就返回 false;如果全部匹配,则返回 true。
  • noneMatch(),只要有一个元素匹配传入的条件,就返回 false;如果全部匹配,则返回 true。
package demo;

import org.junit.Test;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

/**
 * Stream的3个步骤
 * 1、创建stream
 * 2、中间操作
 * 3、终止操作(终端操作)
 */
public class StreamDemo5 {

    List<Employee> employees = Arrays.asList(
            new Employee("zhangsam",10,1000.12, Employee.Status.FREE),
            new Employee("lisi",20,200.12, Employee.Status.BUSY),
            new Employee("wangwu",30,300.12, Employee.Status.VOCATION),
            new Employee("zhangyuting",40,500.12,Employee.Status.BUSY),
            new Employee("tangerrhu",450,500.12,Employee.Status.FREE),
            new Employee("tangerrhu",45,500.12,Employee.Status.VOCATION)
    );

    /**
     * 查找与匹配
     * allMatch—检查是否匹配所有元素
     * anyMatch—检查是否至少匹配一个元素
     * noneMatch—检查是否没有匹配所有元素
     * findFirst—返回第一个元素
     * findAny—返回当前流中的任意元素
     * count—返回流中元素的总个数
     * max—返回流中最大值
     * min—返回流中最小值
     */
    @Test
    public void test1(){
        boolean b1 = employees.stream()
                .allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b1);

        boolean b2 = employees.stream()
                .anyMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
        System.out.println(b2);

        boolean b3 = employees.stream()
                .noneMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
        System.out.println(b3);

        //查询工资最低的人
        Optional<Employee> first = employees.stream()
//                .sorted((e1,e2)->Double.compare(e1.getSalary(),e2.getSalary()))
                .sorted(Comparator.comparingDouble(Employee::getSalary))
                .findFirst();
        System.out.println(first.get());

        //找到Free状态中任意一个人,此处是并行流,多个线程同时过滤
        Optional<Employee> any = employees.parallelStream()
                .filter((e) -> e.getStatus().equals(Employee.Status.FREE))
                .findAny();
        System.out.println(any.get());

        //获取总数
        long count = employees.stream()
                .count();
        System.out.println(count);

        //获取工资最多的人
        Optional<Employee> max = employees.stream()
                .max(Comparator.comparingDouble(Employee::getSalary));
        System.out.println(max.get());

        //打印出最低工资
        Optional<Double> min = employees.stream()
                .map(Employee::getSalary)
                .min(Double::compare);
        System.out.println(min.get());
        
    }
}

5)规约和收集

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串

reduce() 方法的主要作用是把 Stream 中的元素组合起来,它有两种用法:

  • Optional reduce(BinaryOperator accumulator)

没有起始值,只有一个参数,就是运算规则,此时返回 Optional。

  • T reduce(T identity, BinaryOperator accumulator)

有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致。

package demo;

import org.junit.Test;

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

public class StreamDemo6 {

    List<Employee> employees = Arrays.asList(
            new Employee("zhangsam",10,1000.12, Employee.Status.FREE),
            new Employee("lisi",20,200.12, Employee.Status.BUSY),
            new Employee("wangwu",30,300.12, Employee.Status.VOCATION),
            new Employee("zhangyuting",40,500.12,Employee.Status.BUSY),
            new Employee("tangerrhu",450,500.12,Employee.Status.FREE),
            new Employee("tangerrhu",45,500.12,Employee.Status.VOCATION)
    );

    /**
     * 规约
     * reduce() (T identity,BinaryOperator) / reduce(BinaryOperator) —可以将流中元素反复结合起来,得到一个值
     * 起始值作为x,集合中的元素作为y
     * 此处0作为起始值x,集合中的元素作为y,不断的将y和x相加
     */
    @Test
    public void test1(){
        List<Integer> list = Arrays.asList(2,5,9,3,5,1,8,4,7);
        Integer sum = list.stream()
                .reduce(0, (x, y) -> x + y);
        System.out.println(sum);
    }

    /**
     * 问题:为什么test1返回的就是List,而test2返回的是Optional?
     * 因为test1不会是空值
     * 而test2中有可能返回的是空值
     * 有可能为空的值就封装到optional中,避免空指针
     */
    //计算工资的总和
    @Test
    public void test2(){
        Optional<Double> salSum = employees.stream()
                .map(Employee::getSalary)
                .reduce(Double::sum);
        System.out.println(salSum.get());
    }

    /**
     * 收集
     * collect—将流转换为其他形式,接收一个Collection接口的实现,用于给Stream中元素做汇总的方法
     */
    //将员工的名字提取出来
    @Test
    public void test3(){
        List<String> list = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());
        list.forEach(System.out::println);
    }

    //将员工的名字提取出来,要去重,需要将数据收集到set里面
    @Test
    public void test3_2(){
        Set<String> set = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toSet());
        set.forEach(System.out::println);
    }

    //将员工的名字提取出来,要去重,需要将数据收集到set里面,此处使用hashSet
    @Test
    public void test3_3(){
        HashSet<String> hashSet = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(HashSet::new));
        hashSet.forEach(System.out::println);
    }

    //求总数
    @Test
    public void test4(){
        Long count = employees.stream()
                .collect(Collectors.counting());
        System.out.println(count);
    }

    //求工资平均值
    @Test
    public void test5(){
        Double avgSal = employees.stream()
                .collect(Collectors.averagingDouble(Employee::getSalary));
        System.out.println(avgSal);
    }

    //求工资总和
    @Test
    public void test6(){
        Double avgSum = employees.stream()
                .collect(Collectors.summingDouble(Employee::getSalary));
        System.out.println(avgSum);
    }

    //获取工资最高的员工信息
    @Test
    public void test7(){
        Optional<Employee> maxSal = employees.stream()
//                .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
                .collect(Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary)));
        System.out.println(maxSal.get());
    }

    //获取最低工资
    @Test
    public void test8(){
        Optional<Double> minSal = employees.stream()
                .map(Employee::getSalary)
                .collect(Collectors.minBy(Double::compare));
        System.out.println(minSal.get());
    }

    //按员工状态分组
    @Test
    public void test9(){
        Map<Employee.Status, List<Employee>> status = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus));
        System.out.println(status);
    }

    //多级分组,先按状态分,再按年龄分
    @Test
    public void test10(){
        Map<Employee.Status, Map<String, List<Employee>>> map = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
                    if (((Employee) e).getAge() <= 30) {
                        return "青年";
                    } else if (((Employee) e).getAge() <= 50) {
                        return "中年";
                    } else {
                        return "老年";
                    }
                })));
        System.out.println(map);
    }

    //以true和false分区查询,满足条件的一个区,不满足条件的另一个区
    @Test
    public void test11(){
        Map<Boolean, List<Employee>> map = employees.stream()
                .collect(Collectors.partitioningBy((e) -> e.getSalary() > 480));
        System.out.println(map);
    }

    //获取总和、平均值、最大最小等等的另一个方法 summarizingDouble
    @Test
    public void test12(){
        DoubleSummaryStatistics map = employees.stream()
                .collect(Collectors.summarizingDouble((e) -> e.getSalary()));
        System.out.println(map.getMax());
        System.out.println(map.getCount());
        System.out.println(map.getMin());
        System.out.println(map.getSum());
        System.out.println(map.getAverage());
    }

    //字符串连接
    @Test
    public void test13(){
        String s = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.joining(","));
        System.out.println(s);
    }
}

运算规则可以是 Lambda 表达式(比如 (a, b) -> a + b),也可以是类名::方法名(比如 Integer::sum)。

转换流collect介绍

既然可以把集合或者数组转成流,那么也应该有对应的方法,将流转换回去——collect() 方法就满足了这种需求。

toArray() 方法可以将流转换成数组,那String[]::new,是什么东东呢?来看一下 toArray() 方法的源码。

<A> A[] toArray(IntFunction<A[]> generator);

也就是说 String[]::new 是一个 IntFunction,一个可以产生所需的新数组的函数,可以通过反编译字节码看看它到底是什么:

String[] strArray = (String[])list.stream().toArray((x$0) -> {
    return new String[x$0];
});
System.out.println(Arrays.toString(strArray));

也就是相当于返回了一个指定长度的字符串数组。

当我们需要把一个集合按照某种规则转成另外一个集合的时候,就可以配套使用 map() 方法和 collect() 方法。

List<Integer> list1 = list.stream().map(String::length).collect(Collectors.toList());

通过 stream() 方法创建集合的流后,再通过 map(String:length) 将其映射为字符串长度的一个新流,最后通过 collect() 方法将其转换成新的集合。

Collectors 是一个收集器的工具类,内置了一系列收集器实现,比如说 toList() 方法将元素收集到一个新的 java.util.List 中;比如说 toCollection() 方法将元素收集到一个新的 java.util.ArrayList 中;比如说joining() 方法将元素收集到一个可以用分隔符指定的字符串中。

Stream api 部分练习

package demo;

import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

public class StreamDemo7 {

    List<Employee> employees = Arrays.asList(
            new Employee("zhangsam",10,1000.12, Employee.Status.FREE),
            new Employee("lisi",20,200.12, Employee.Status.BUSY),
            new Employee("wangwu",30,300.12, Employee.Status.VOCATION),
            new Employee("zhangyuting",40,500.12,Employee.Status.BUSY),
            new Employee("tangerrhu",450,500.12,Employee.Status.FREE),
            new Employee("tangerrhu",45,500.12,Employee.Status.VOCATION)
    );

    /**
     * 1、给定一个数字列表,返回一个由每个数的平方构成的列表
     * 给定[1,2,3,4,5],返回[1,4,9,16,25]
     */
    @Test
    public void test1(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        Stream<Integer> integerStream = list.stream()
                .map((e) -> e * e);
        integerStream.forEach(System.out::println);
    }

    /**
     * 用map和reduce算出流中有多少employee
     */
    @Test
    public void test2(){
        Optional<Integer> sum = employees.stream()
                .map((e) -> 1)
                .reduce(Integer::sum);
        System.out.println(sum.get());
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值