前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Lambda与Stream✨让代码简洁高效的七大原则

Lambda与Stream✨让代码简洁高效的七大原则

原创
作者头像
菜菜的后端私房菜
发布2024-07-29 09:21:48
420
发布2024-07-29 09:21:48
举报
文章被收录于专栏:深入浅出Java

Lambda与Stream✨让代码简洁高效的七大原则

在现代Java编程实践中,Lambda表达式和Stream API已成为提高代码可读性和执行效率的重要工具

本文基于 Effective Java Lambda与Stream章节汇总出7条相关原则(文末附案例链接)

Lambda优于匿名内部类

JDK8中只存在一个抽象方法的接口称为函数接口,并使用注解@FunctionalInterface标识

代码语言:java
复制
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    //...
}

在此之前,在方法中实现这种接口通常会使用匿名内部类

代码语言:java
复制
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
list.sort(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
});
//[6, 5, 4, 3, 2, 1]
System.out.println(list);

JDK 8时可以使用Lambda表达式来实现函数接口

代码语言:java
复制
list.sort((o1, o2) -> o2.compareTo(o1));

对于这种简单易懂的函数接口使用Lambda表达式更容易实现、简洁 Lambda表达式优于匿名内部类

善用方法引用

JDK 8 还提供方法引用,一般情况下方法引用会比Lambda表达式还简洁

比如上面那个倒转排序可以使用方法引用来实现

代码语言:java
复制
list.sort((o1, o2) -> o2.compareTo(o1));
//方法引用
list.sort(Comparator.reverseOrder());

实际上会调用其内部的方法

代码语言:java
复制
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
    return Collections.reverseOrder();
}

但是在某些场景下,使用方法引用反而会太过简洁导致看不懂,因此哪种方式易懂就优先使用哪种方式

坚持使用标准函数接口

JDK 8 java.util.function提供几十种标准函数接口,其只需要记住基础函数接口,其他都是变体

Predicate 谓词 返回布尔类型 判断给定输入参数是否满足某种条件

代码语言:java
复制
        @FunctionalInterface
        public interface Predicate<T> {
            boolean test(T t);
        }
代码语言:java
复制
        // 创建一个谓词,检查数字是否是偶数
        Predicate<Integer> isEven = n -> n % 2 == 0;

        // 使用谓词过滤列表
        numbers.stream()
                .filter(isEven)
                .forEach(System.out::println);

Supplier 不需要传参提供一个结果

代码语言:java
复制
        @FunctionalInterface
        public interface Supplier<T> {
            T get();
        }
代码语言:java
复制
        // 创建一个Supplier,每次调用get方法都会生成一个新的随机数
        Supplier<Integer> randomIntSupplier = () -> (int) (Math.random() * 100);

        // 获取并打印5个随机数
        for (int i = 0; i < 5; i++) {
            System.out.println(randomIntSupplier.get());
        }

Consumer 消费 传入参数不返回

代码语言:java
复制
        @FunctionalInterface
        public interface Consumer<T> {
            void accept(T t);
        }
代码语言:java
复制
        List<String> greetings = Arrays.asList("Hello", "World", "!");

        // 创建一个consumer,用于打印接收到的消息
        Consumer<String> printer = message -> System.out.println(message);

        // 对列表中的每个元素应用consumer
        greetings.forEach(printer);

Function 函数 传入T类型响应另一个R类型

代码语言:java
复制
        @FunctionalInterface
        public interface Function<T, R> {
            R apply(T t);
        }
代码语言:java
复制
        List<String> names = Arrays.asList("John Doe", "Jane Smith", "Alice Johnson");

        // 创建一个Function,将人名转换为姓氏
        Function<String, String> getLastName = name -> name.split(" ")[1];

        // 将所有名字转换为姓氏
        List<String> lastNames = names.stream()
                .map(getLastName)
                .collect(Collectors.toList());

        // 输出:[Doe, Smith, Johnson]
        System.out.println(lastNames);  

当我们设计时优先使用标准函数接口,标准函数接口无法满足我们的需求时再自定义函数接口

(记得使用注解@FunctionalInterface)

谨慎使用Stream

JDK8提供流式处理,先将集合转换为流,再通过多重管道对流进行处理,最后调用终止操作结束

Stream还是链式编程,给编写代码带来极大简便

并不是所有处理都使用Stream会更好,如果业务中流程复杂、滥用Stream会降低代码的可读性、维护性

这种情况下Stream配合for循环迭代可能会更好

最好避免使用Stream来处理char类型,chars返回的实际上是IntStream

代码语言:java
复制
        //3375633756303402151831471311692515133756
        "菜菜的后端私房菜".chars().forEach(System.out::print);
        //菜菜的后端私房菜
        "菜菜的后端私房菜".chars().forEach(x -> System.out.print((char) x));
优先选择Stream中无副作用的函数

副作用的函数指的是处理数据的同时改变原集合,比如 foreach

无副作用的函数则在处理过程中不影响原集合,比如filter、map、sorted

代码语言:java
复制
        List<String> list = Arrays.asList("aaa", "b", "cc");

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

使用完无副作用函数后再使用收集器转换为理想的容器

Stream优先用Collection为返回类型

使用Stream处理时,有些情况后续需要使用迭代,有些情况后续需要使用Stream

为了兼容多种情况,返回时应该优先使用Collection类型

比如Collection、Set、List等,它们都能转换为Stream或迭代器

代码语言:java
复制
        List<String> lists = Arrays.asList(" 菜菜", " caicai ", "小菜 ", "", " ");
        Stream<String> stream = lists.stream()
                .filter(s -> !Objects.equals("", s.trim()))
                .map(s -> s.trim());

        //Collection List Set
        Collection<String> collect = stream.collect(Collectors.toCollection(ArrayList::new));
		//获取迭代
        Iterator<String> iterator = collect.iterator();
		//获取Stream
        Stream<String> collectionStream = collect.stream();

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        collectionStream.forEach(System.out::println);
谨慎使用Stream并行

多线程并行能够提高处理程序的速度,同时不熟悉并行时误操作也会带来数据一致性问题

并行最好使用在互不干扰的情况,避免出现数据不一致

比如数组长度为100,使用十个线程,每个线程负责处理十个长度的区间,并行处理时互不影响

比如ArrayList、HashMap等都是直接/间接基于数组实现的,使用并行加快速度

使用parallel()开启并行

代码语言:java
复制
        static long piParallel(long n) {
            return LongStream.rangeClosed(2, n)
                    .parallel()
                    .mapToObj(BigInteger::valueOf)
                    .filter(i -> i.isProbablePrime(50))
                    .count();
        }

测试并行提升速度

代码语言:java
复制
        long start = System.currentTimeMillis();
        piParallel(10_000_000);
        // 2150
        System.out.println(System.currentTimeMillis() - start);

        start = System.currentTimeMillis();
        pi(10_000_000);
        // 16363
        System.out.println(System.currentTimeMillis() - start);
总结

函数接口只存在一个抽象方法,并用注解@FunctionalInterface标识,可以使用Lambda表达式实现

简单易懂的函数接口使用Lambda实现简洁,优于匿名内部类

方法引用比Lambda更简洁,但某些情况下太简介会降低可读性,哪种方式更易提示代码可读性选择哪种

java.util.function提供标准函数接口,当设计组件时优先选择标准函数接口,不满足需求再自定义

Stream流式处理能够给编写代码带来极大简便,但业务代码流程复杂,滥用Stream会降低代码可读性、维护性,最好结合Stream和迭代的方式写出可读性、可维护性高的代码

避免使用Stream处理char类型,会转化为Int类型处理

在Stream中优先使用不影响原集合的方法,如filter、map、sorted等,等处理完数据后再通过收集器转化为对应容器

在某些场景下,后续需要使用Stream或迭代,Collection都兼容,优先返回Collection、List、Set

并行能够加快程序运行速度,当可能带来线程不安全的一致性问题

使用并行最好互不干扰,比如数组实现的容器(ArrayList、HashMap),线程各自负责自己的区间

最后(不要白嫖,一键三连求求拉~)

本篇文章被收入专栏 Effective Java,感兴趣的同学可以持续关注喔

本篇文章笔记以及案例被收入 Gitee-CaiCaiJavaGithub-CaiCaiJava,除此之外还有更多Java进阶相关知识,感兴趣的同学可以starred持续关注喔~

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

关注菜菜,分享更多技术干货,公众号:菜菜的后端私房菜

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Lambda与Stream✨让代码简洁高效的七大原则
    • Lambda优于匿名内部类
      • 善用方法引用
        • 坚持使用标准函数接口
          • 谨慎使用Stream
            • 优先选择Stream中无副作用的函数
              • Stream优先用Collection为返回类型
                • 谨慎使用Stream并行
                  • 总结
                    • 最后(不要白嫖,一键三连求求拉~)
                    相关产品与服务
                    容器服务
                    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档