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

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

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

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

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

image.png

Lambda优于匿名内部类

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

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    //...
}

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

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表达式来实现函数接口

list.sort((o1, o2) -> o2.compareTo(o1));

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

善用方法引用

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

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

list.sort((o1, o2) -> o2.compareTo(o1));
//方法引用
list.sort(Comparator.reverseOrder());

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

public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
    return Collections.reverseOrder();
}

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

坚持使用标准函数接口

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

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

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

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

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

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

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

Consumer 消费 传入参数不返回

        @FunctionalInterface
        public interface Consumer<T> {
            void accept(T t);
        }
        List<String> greetings = Arrays.asList("Hello", "World", "!");

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

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

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

        @FunctionalInterface
        public interface Function<T, R> {
            R apply(T t);
        }
        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

        //3375633756303402151831471311692515133756
        "菜菜的后端私房菜".chars().forEach(System.out::print);
        //菜菜的后端私房菜
        "菜菜的后端私房菜".chars().forEach(x -> System.out.print((char) x));

优先选择Stream中无副作用的函数

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

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

        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或迭代器

        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()开启并行

        static long piParallel(long n) {
            return LongStream.rangeClosed(2, n)
                    .parallel()
                    .mapToObj(BigInteger::valueOf)
                    .filter(i -> i.isProbablePrime(50))
                    .count();
        }

测试并行提升速度

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

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

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

相关文章
|
安全 Go
Go语言封装艺术:代码安全与结构清晰
Go语言封装艺术:代码安全与结构清晰
82 0
|
5月前
|
Serverless 开发者 Python
Python函数式编程:让你的代码更简洁、更高效!
【6月更文挑战第12天】Python函数式编程引入了数学函数概念,强调无副作用和不可变数据。特点包括高阶函数、Lambda表达式、map、filter和reduce。示例展示了如何使用map进行平方运算,filter筛选条件元素,reduce计算元素总和,体现其简洁高效。虽然不适用于所有情况,但函数式编程能提升代码可读性和可维护性。
32 3
|
6月前
|
存储 安全 Java
12条通用编程原则✨全面提升Java编码规范性、可读性及性能表现
12条通用编程原则✨全面提升Java编码规范性、可读性及性能表现
|
Go 开发者
一文详解Go语言接口嵌套组合的精髓!
一文详解Go语言接口嵌套组合的精髓!
185 0
|
6月前
|
Java
【专栏】Java 8 的 Streams 提供了一种处理数据集合的新方式,增强了代码的可读性和可维护性
【4月更文挑战第28天】Java 8 的 Streams 提供了一种处理数据集合的新方式,增强了代码的可读性和可维护性。本文介绍了 Streams 的基本概念,如从数据源创建 Stream,以及中间和终端操作。通过过滤、映射、归并、排序、分组等案例,展示了 Streams 的使用,包括并行 Streams 提高效率。学习 Streams 可以提升代码质量和效率,文章鼓励读者在实际开发中探索更多 Streams 功能。
43 0
|
6月前
|
编译器 程序员 C++
C++从入门到精通:3.1模板编程——提高代码的复用性和灵活性
C++从入门到精通:3.1模板编程——提高代码的复用性和灵活性
|
C语言
C数组超细节精髓
C数组超细节精髓
79 0
|
Go 开发者
Go语言代码优雅之道:简洁、清晰示例驱动
Go语言代码优雅之道:简洁、清晰示例驱动
142 0
|
Java
lambda表达式的进阶过程(二)
lambda表达式的进阶过程
63 0
lambda表达式的进阶过程(二)
|
缓存 Java 程序员
函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码
本文会以惰性加载为例一步步介绍函数式编程中各种概念,所以读者不需要任何函数式编程的基础,只需要对 Java 8 有些许了解即可。
函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码