代码看起来一点也不优雅,试试Stream

2023-03-07 09:56:07 浏览数 (2)

前言

JDK的发行版本都已经衍生至19了,这个从8就引入的Stream流应当是属于Java程序员基操了。然而最近面试遇到开发经验3年的工程师,对它似乎不是很熟悉,让我大吃一惊。本文通过常用的一些简单的例子把它以最小的时间成本给大家说明白。

一、楔子

在使用集合的时候迭代往往是使用的最多的,对比是否使用stream的代码实现,

代码语言:javascript复制
    public int calcSum(List<Integer> list) {
        int sum = 0;
        for (int i = 0; i < list.size(); i  ) {
            sum  = list.get(i);
        }
        return sum;
    }
    
    public int calcSumLambda(List<Integer> list) {
        return list.stream().mapToInt(x -> x).sum();
    }

第一次看到这样的写法时,可能会认为这样的代码可读性不高,但当你熟悉之后,你会改变对它的看法。

二、如何创建流

想要使用Stream,首先需要创建一个流,最常见的是直接调用集合的java.util.Collection#stream方法

代码语言:javascript复制
    private void createByCollection() {
        List<Integer> list = new ArrayList<>();
        Stream<Integer> stream = list.stream();
    }

当然通过数组同样能够创建

代码语言:javascript复制
    private void createByArrays() {
        Integer[] array1 = {1, 2, 3};
        Integer[] array2 = new Integer[] {1, 2, 3};
        Stream<Integer> stream1 = Stream.of(array1);
        Stream<Integer> stream2 = Arrays.stream(array1);
    }

三、流操作的分类

Stream流操作共分为两个大类:惰性求值、及早求值

代码语言:javascript复制
    /**
     * 通过Stream流过滤元素返回新的集合
     * 
     * @param list 待过滤的集合
     * @return 新的集合
     */
    private List<Integer> filterByStream(List<Integer> list) {
        return list.stream().filter(number -> number > 1).collect(Collectors.toList());
    }

Stream操作时,先调用了filter方法传入了一个Lambda表达式代表过滤规则,后调用了collect方法表示将流转换为List集合。

我们不需要去记哪些方法是惰性求值,如果方法的返回值是Stream那么它代表的就是惰性求值。如果返回另外一个值或空,那么它代表的就是及早求值。

这些流操作定义之后,在程序中是怎么调用的定义的lambda表达式的?

例如在java.util.stream.ReferencePipeline抽象类中有对Stream接口collect的实现,方法由final修饰,不再支持重写。

代码语言:javascript复制
    @Override
    @SuppressWarnings("unchecked")
    public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) {
        A container;
        if (isParallel()
                && (collector.characteristics().contains(Collector.Characteristics.CONCURRENT))
                && (!isOrdered() || collector.characteristics().contains(Collector.Characteristics.UNORDERED))) {
            container = collector.supplier().get();
            BiConsumer<A, ? super P_OUT> accumulator = collector.accumulator();
            forEach(u -> accumulator.accept(container, u));
        }
        else {
            container = evaluate(ReduceOps.makeRef(collector));
        }
        return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)
               ? (R) container
               : collector.finisher().apply(container);
    }

四、常用基操

map

映射,x->y,转换数据类型

代码语言:javascript复制
    /**
     * 通过Stream map操作将小写的字符串集合转换为大写
     * 
     * @param list 小写字符串集合
     * @return 大写字符串集合
     */
    public List<String> toUpperByStreamMap(List<String> list) {
        // return list.stream().map(String::toUpperCase).collect(Collectors.toList());
        return list.stream().map(string -> string.toUpperCase()).collect(Collectors.toList());
    }
filter

过滤,“排除不符合某个条件的元素”,也就是返回true的时候保留,返回false排除

代码语言:javascript复制
    /**
     * 通过Stream filter筛选出分数大于60分的学生集合
     * 
     * @param students 待过滤的学生集合
     * @return 分数大于60分的学生集合
     */
    public List<Student> fetchPassedStudentsByStreamFilter(List<Student> students) {
        return students.stream().filter(student -> student.getScore().compareTo(60) >= 0).collect(Collectors.toList());
    }
sorted

排序,

代码语言:javascript复制
    /**
     * 学生按分数升序排列
     * 
     * @param students 学生集合
     * @return 排序后的学生集合
     */
    private List<Student> sortedByStreamSorted(List<Student> students) {
        return students.stream().sorted(Comparator.comparing(Student::getScore)).collect(Collectors.toList());
    }

如果要降序(大-->小),仅需再调用reversed方法Comparator.comparing(Student::getScore).reversed())这就是声明式编程,你只管叫它做什么,而不像命令式编程叫它如何做。

reduce

对于reduce操作,不建议在现实中使用。

如果你有累加、求最大值、最小值的需求,Stream封装了更简单的方法。

代码语言:javascript复制
    /**
     * T reduce(T identity, BinaryOperator<T> accumulator);
     * 赋初始值为1,对集合中的元素进行累加
     * 
     * @param numbers 集合元素
     * @return 累加结果
     */
    private Integer calcTotal2(List<Integer> numbers) {
        return numbers.stream().reduce(1, (total, number) -> total   number);
    }
min || max

顾名思义,求取集合中的最小值和最大值。

Java8对Comparator接口提供了新的静态方法comparing,这个方法返回Comparator对象,以前我们需要手动实现compare比较,现在我们只需要调用Comparator.comparing静态方法即可。

代码语言:javascript复制
    /**
     * 通过Stream min计算集合中的最小值
     * 
     * @param numbers 集合
     * @return 最小值
     */
    private Integer minByStreamMin(List<Integer> numbers) {
        return numbers.stream().min(Comparator.comparingInt(Integer::intValue)).get();
    }

    /**
     * 通过Stream min计算学生集合中的最低成绩
     * 
     * @param students 学生集合
     * @return 最低成绩
     */
    private Double minScoreByStreamMin(List<Student> students) {
        Student minScoreStudent = students.stream().min(Comparator.comparing(Student::getScore)).get();
        return minScoreStudent.getScore();
    }

summaryStatistics

求和操作也是常用的操作,利用reduce会让代码晦涩难懂,特别是复杂的对象类型。

代码语言:javascript复制
    /**
     * 学生类型的集合常用计算
     * 
     * @param students 学生
     */
    private void calc(List<Student> students) {
        DoubleSummaryStatistics summaryStatistics =
            students.stream().mapToDouble(Student::getScore).summaryStatistics();
        System.out.println("平均分:"   summaryStatistics.getAverage());
        System.out.println("总分:"   summaryStatistics.getSum());
        System.out.println("最高分:"   summaryStatistics.getMax());
        System.out.println("最低分:"   summaryStatistics.getMin());
    }

Collectors

前面的大部分操作都是以collect(Collectors.toList())结尾,看多了自然也大概猜得到它是将流转换为集合对象。最大的功劳当属Java8新提供的类——Collectors收集器。

示例给出比较常见的List和Map的转换,

代码语言:javascript复制
    /**
     * 将学生类型的集合转换为只包含名字的集合
     * 
     * @param students 学生集合
     * @return 学生姓名集合
     */
    private List<String> translateNames(List<Student> students) {
        return students.stream().map(Student::getStudentName).collect(Collectors.toList());
    }

    /**
     * 将学生类型的集合转换为Map类型,key=学号,value=学生
     * 
     * @param students 学生集合
     * @return 学生Map
     */
    private Map<Long, Student> translateStudentMap(List<Student> students) {
        return students.stream().collect(Collectors.toMap(Student::getStudentNumber, student -> student));
    }

小结

以上内容没有多高深,主要为了让大家知道不同情景下该使用哪种API,如果有同学都不熟悉这些,还怎么写出优雅的代码呢,面试通过也成问题,像我就喜欢编程功底扎实的(手动狗头)。

0 人点赞