Skip to content

java8流的使用

基础数据

Student类

@Data
public class Student {

    /** 学号 */
    private String studentId;

    /** 姓名 */
    private String name;

    /** 年龄 */
    private int age;

    /** 性别 m-男,w-女 */
    private String sex;

    /** 体重 */
    private double weight;

    /** 高度 */
    private double height;

    public Student(String studentId,String name,int age,String sex,double weight,double height){
        this.studentId = studentId;
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.weight = weight;
        this.height = height;
    }
}

公共的资源(自定义List和Map)

/**
 * @Description 公共的资源
 * @Author lcy
 * @Date 2020/4/14 16:53
 */
public class Util {

    public static Map<String,Student> studentMap = new HashMap<>();

    public static List<Student> studentList = new ArrayList<>();

    static{
        String[] nameArr = {"张三","李四","王五","赵六","钱七","孙八","周九","吴十","郑十一"};
        int[] ageArr = {20,21,22,23,24,25,26};
        int height = 160;
        int weight = 120;
        for (int i = 0; i < 9; i++) {
            String studentId = (100 + i * ageArr.length * 10) + "";
            int ageIndex = i % (ageArr.length-1);
            String sex = i % 2 == 0 ? "w" : "m";
            int newHeight = height + i % 3 * 10;
            int newWeight = weight + i % 3 * 10;
            Student student = new Student(studentId,nameArr[i],ageArr[ageIndex]
                    ,sex,newWeight,newHeight);
            studentList.add(student);
            studentMap.put(student.getStudentId(),student);
        }

    }

}

一、筛选(中间操作)

使用谓词筛选,filter筛选条件范围内的数据,distinct去重,limit截断流。skip相反的是跳过元素,如果

Util.studentList.stream()
                //筛选年龄大于21岁的
                .filter(student -> student.getAge() > 21)
                //去重
                .distinct()
                //截断,只取前4个
                .limit(4)
                //跳过前面两个
                .skip(2)
                //转成集合
                .collect(toList());

二、映射(中间操作)

使用map会接受一个函数作为一个参数,这个参数会应用到(list)每个元素上,映射成一个新的元素。

Util.studentList.stream()
               //将结果由Stream<Student>转成Stream<String>
               .map(Student::getName)
               //转成集合
               .collect(toList());

使用flatmap将流进行扁平化,使用flatMap方法的效果是,map以后各个元素并不是分别映射成一个流,而是映射成流的内容。所 有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。

List<Integer> bbb = Arrays.asList(1,2,3);
List<Integer> ccc = Arrays.asList(4,5);

bbb.stream()
        //如果用这里用map,流的数据为Stream<Integer[]>,用flatmap就变成Stream<Integer>
        .flatMap(bbbb -> ccc.stream()
        //能够被3整除的数组
        .filter(cccc -> (bbbb + cccc) % 3 == 0)
        //组成新的数组
        .map(cccc -> new int[]{bbbb,cccc}))
        //输出
        .forEach(ab -> System.out.println(Arrays.toString(ab)));

三、查找和匹配(终端操作)

allMatch是否所有元素都匹配,noneMatch是否没有元素匹配,anyMatch是否最少一个元素匹配,最终都返回一个boolean类型的值

Util.studentList.stream()
                //判断是否有人的年龄大于100,如果列表里有一个,就是true
                .anyMatch(student -> student.getAge() > 100);
        Util.studentList.stream()
                //判断是否所有人的身高都大于120
                .allMatch(student -> student.getHeight() > 120);
        Util.studentList.stream()
                //判断是否列表里没有一个人的身高大于120
                .noneMatch(student -> student.getHeight() > 120);

findFirst()是返回第一个元素。 findAny()一般来说如果使用的是串行stream流的形式,与findFirst一样返回第一个元素,如果使用的是并行流parallelStream,从当前流的列表里获取一个随机元素返回 这两个操作返回的都是Optional<T>,利用Optional,避免和null检查相关的bug

Util.studentList.stream().filter(student -> student.getAge() > 19).forEach(System.out::println);
        System.out.println("-------------------");
        Optional<Student> any = Util.studentList
                //并行流,多个中间操作并行操作,stream是串行流,如果用stream,findAny返回的结果与findFirst一样都是第一个元素
                .parallelStream()
                .filter(student -> student.getAge() > 19)
                .findAny();
        any.ifPresent(System.out::println);

四、归约(终端操作)

使用reduce()将所有元素反复结合起来(可以理解为运算),得到一个值。例如元素的求和

Double reduce = Util.studentList.stream()
               .map(Student :: getHeight)
               //0是初始值,第一个元素计算的时候与初始值计算
               //.reduce((double)0,(a,b) -> a + b);
               //另外一种求和方式
               .reduce((double)0,Double::sum);

五、排序、最大值、最小值

sorted排序,默认从小到大排序,max获取最大值,min获取最小值,如果最大值或最小值有多个,只会返回一个值

Util.studentList.stream()
                //将结果由Stream<Student>转成Stream<String>
                .map(Student :: getStudentId)
                //默认排序方式 从小到大
                .sorted()
                .forEach(System.out::println);
                //转成集合
        //.collect(toList());
        Util.studentList.stream()
                //等于 map和sort结合
                .sorted(comparing(Student::getStudentId))
                .forEach(System.out::println);
        System.out.println("min--------------------------------max");

        Util.studentList.stream()
                .filter(student -> student.getAge()>30)
                //将结果由Stream<Student>转成Stream<String>
                .map(Student :: getStudentId)
                //获取最大学号
                .max(String::compareTo)
                //如果值不为空执行函数操作
                .ifPresent(System.out::println);
        Util.studentList.stream()
                //将结果由Stream<Student>转成Stream<Integer>
                .map(Student :: getAge)
                //获取最小学号
                .min(Integer::compareTo)
                .ifPresent(System.out::println);

六、数值流(避免装箱和拆箱)

将Integer的流类型映射成int,这样就避免装箱。使用boxed将int的流转成Integer。

Util.studentList.stream()
                //将结果由Stream<Student>转成IntStream
                .mapToInt(Student :: getAge)
                .max();
        //如果max为空的情况下,给定一个值设置默认值,并且返回
        int maxInt = max.orElse(1);
        Util.studentList.stream()
                //将结果由Stream<Student>转成IntStream
                .mapToInt(Student :: getAge)
                //将结果由IntStream转成Stream<Integer>
                .boxed()
                .max(Integer::compareTo);

数值范围 生成范围内数值的流,根据IntStream的静态方法rangeClosed(start,end)和range(start,end),start参数是起始值,end是结束值。 range的范围是[start,end),rangeClosed的范围是[start,end]

IntStream range = IntStream
                .range(0,100);
        IntStream rangeClosed = IntStream
                .rangeClosed(0,100);

通过数值流IntStream、DoubleStream、LongStream的无限流生成数列。 IntSupplier匿名类可以通过字段定义状态,而状态又可以用getAsInt方法来修改。这是一个副作用的例子。Lambda都是没有副作用的;它们没有改变任何状态。使用iterate的方法则是纯粹不变的:它没有修改现有状态,但在每次迭代时会创建新的元组

Stream<Integer> iterate = Stream
                //创建无限流
                .iterate(0,a -> a + a)
                //必须截断,否则会一直循环创建
                .limit(10);
        Stream
                //生成菲波那切数列
                .iterate(new int[]{0,1},n -> new int[]{n[1],n[0] + n[1]})
                .limit(10).forEach((a)-> System.out.println(Arrays.toString(a)));
        IntStream
                // 创建无限流  只生成1
                //.generate(() -> 1)
                //生成带函数的斐波那契数列
                .generate(new IntSupplier() {

                    private int a = 0;

                    private int b = 1;

                    @Override public int getAsInt(){
                        int temp = a;
                        a = b;
                        b = temp + b;
                        return b;
                    }
                })
                .limit(5).forEach(System.out::println);

七、并行流

以下列表是一些流的数据源对于并行的可分解性

数据源可分解性
ArrayList极佳
LinkedList
IntStream.range极佳
Stream.iterate
HashSet
TreeSet

并行——使用流还是CompletableFutures?

  1. 如果你进行的是计算密集型的操作,并且没有I/O,那么推荐使用Stream接口,因为实现简单,同时效率也可能是最高的(如果所有的线程都是计算密集型的,那就没有必要创建比处理器核数更多的线程)。
  2. 如果你并行的工作单元还涉及等待I/O的操作(包括网络连接等待),那么使用CompletableFuture灵活性更好,你可以像前文讨论的那样,依据等待/计算,或者W/C的比率设定需要使用的线程数。这种情况不使用并行流的另一个原因是,处理流的流水线中如果发生I/O等待,流的延迟特性会让我们很难判断到底什么时候触发了等待。

九、流问题排查

如果使用for-each是会恢复流的正常使用,无法判断异常的出处,所以使用peek,在每次操作前进行一次操作

Util.studentList.stream()
                //插入一个打印的动作
                .peek(student -> System.out.println("peek1"))
                .map(Student::getStudentId)
                //插入一个打印的动作
                .peek(student -> System.out.println("peek2"))
                .collect(toList())
                //forEach会恢复流的运行。出现异常无法顺利调试
                .forEach(System.out::println);

十、终端筛选

使用groupingBy可以筛选将list转成map<k,List>、map<k,Set>等,根据groupingBy和其它收集器转换成想要的数据。 toSet方法返回的Set是什么类型并没有任何保证,通过自定义的toCollection(HashSet::new)可以得到解决

Util.studentList.stream().collect(
                //两层分组,第一层,把性别分组
                groupingBy(Student :: getSex
                        //把年龄再分组
                        ,groupingBy(Student :: getAge))

        ).forEach((k,v) -> System.out.println(k + ":" + v));
        Util.studentList.stream().collect(
                //把性别分组,不同性别的高度求和
                groupingBy(Student :: getSex
                        //把年龄再分组,返回的是Map<String,List<Studen>>
                        ,summingDouble(Student :: getHeight)))
                .forEach((k,v) -> System.out.println(k + ":" + v));
        System.out.println("---------------------------");
        Util.studentList.stream().collect(
                //把性别分组,不同性别的高度求和
                groupingBy(Student :: getSex,
                        //把年龄再分组,返回的是Map<String,Set<Double>>
                        //toset没有保证set的类型
                        //mapping(Student :: getHeight,toSet())))
                        mapping(Student::getHeight,toCollection(HashSet ::new))))
                .forEach((k,v) -> System.out.println(k + ":" + v));
        Util.studentList.stream().collect(
                //根据性别分类,男的为true,女的为false
        partitioningBy((student) -> student.getSex().equals("m")))
                .forEach((k,v) -> System.out.println(k + ":" + v));

        Util.studentList.stream().collect(
                //根据性别分类,男的为true,女的为false
                partitioningBy((student) -> student.getSex().equals("m"),
                        //二次分组
                        groupingBy(Student::getAge)))
                .forEach((k,v) -> System.out.println(k + ":" + v));

分区函数是分组函数中特殊的一种,根据boolean值来进行筛选分区

Util.studentList.stream().collect(
                //根据性别分类,男的为true,女的为false
        partitioningBy((student) -> student.getSex().equals("m")))
                .forEach((k,v) -> System.out.println(k + ":" + v));

通过终端minBy和maxBy筛选最大值和最小值、summiingInt、summingLong、summingDouble求和,与max和min一样。

Optional<Student> collect = Util.studentList.stream()
                //筛选最大年龄的人,首先通过comparing实现对比,再由maxBy筛选,如果最大值或最小值有多个,只会返回一个值
                .collect(minBy(comparing(Student :: getAge)));
        //.ifPresent(System.out::println);

//求年龄的总和
Util.studentList.stream().collect(summingInt(Student::getAge))
//求平均年龄
Util.studentList.stream().collect(averagingInt(Student::getAge));

终端通过joining可以将数据连串起来。还可以根据的参数进行分割开

//连接字符串,将名字用,隔开
        Util.studentList.stream().map(Student :: getName).collect(joining(","));

collectingAndThen用于转换函数返回的类型,包裹另一个收集器,对其结果应用转换函数

int howManyDishes =Util.studentList.stream().collect(collectingAndThen(toList(), List::size));

十一、自定义终端收集器ToListCollector(T)

import static java.util.stream.Collector.Characteristics.*;

/**
 * @Description TODO
 * @Author lcy
 * @Date 2020/4/21 17:57
 */
public class ToListConllector<T> implements Collector<T,List<T>,List<T>> {

    /** 创建集合操作的起始点 */
    @Override public Supplier<List<T>> supplier(){
        //创建一个ArrayList实例装载数据
        return ArrayList ::new;
    }

    /** 遍历过的项目处理。累加 */
    @Override public BiConsumer<List<T>,T> accumulator(){
        //添加数据
        return List::add;
    }

    /** 恒等函数 */
    @Override public Function<List<T>,List<T>> finisher(){
        return Function.identity();
    }

    /** 修改第一个累加器,即 BiConsumer()方法,与第二个累加器合并*/
    @Override public BinaryOperator<List<T>> combiner(){
        return (list1,list2)->{
            list1.addAll(list2);
            //返回修改后的第一个累加器
            return list1;
        };
    }

    /** 为收集器添加 IDENTITY_FINISH,CONCURRENT的标志*/
    @Override public Set<Characteristics> characteristics(){
        return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH,CONCURRENT));
    }
}

调用实例

List<Student> collect = Util.studentList.stream().collect(new ToListConllector<>());
//等同于
List<Student> collect = Util.studentList.stream().collect(
                ArrayList ::new,
                List::add,
                List::addAll);