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

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持续关注喔~

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

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

相关推荐
魔道不误砍柴功1 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2341 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟2 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity3 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天3 小时前
java的threadlocal为何内存泄漏
java