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-CaiCaiJava、 Github-CaiCaiJava,除此之外还有更多Java进阶相关知识,感兴趣的同学可以starred持续关注喔~
有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~
关注菜菜,分享更多技术干货,公众号:菜菜的后端私房菜