Java 8 的 Lambda 表达式已经不再是“新特性”。
曾经很多人抵触 Lambda 表达式,现在几乎也成了标配。
实际开发中最常见的是,很多人使用 Stream
来处理集合类。
但是由于 Lambda 表达式的滥用,代码可读性会变差,那么该如何解决?
本文会讨论一下这个问题,并给出自己的几个解决办法。
对于 Lambda 表达式或者 Stream 的看法不尽一致。
使用 Lambda
表达式可以减少类或者方法的常见。
使用 Stream
可以享受链式编程的乐趣。
有些人看别人都在用,似乎有些高端,或者担心自己被淘汰也跟着大量使用。
有些人对 lambda 表达式持反对意见。
他们认为大量使用 lambda 表达式写出的代码不容易理解。
还有的团队老人比较多,不太容易接受新的事物,不提倡使用 Lambda 表达式。
如:
Stream
的广泛使用带来了很多样板方法。
List<String> tom = dogs.stream().filter(dog -> dog.getName().startsWith("tom")).map(dog -> dog.getName().toLowerCase()).collect(Collectors.toList());
甚至经常有人会在 Stream 的 map 函数中写大量转换代码。
import lombok.Data;
@Data
public class DogDO {
private String name;
private String nickname;
private String address;
private String owner;
}
DogVO 和 DogDO 结构相同。
List<DogVO> result = dogs.stream().filter(dog -> dog.getName().startsWith("tom")).map(dog -> {
DogVO dogVO = new DogVO();
dogVO.setName(dog.getName());
dogVO.setAddress(dog.getAddress());
dogVO.setOwner(dog.getOwner());
return dogVO;
}).collect(Collectors.toList());
更有甚者直接将整个 Stream 表达结果当做参数传入方法中:
result.addAll(dogs.stream().filter(dog -> dog.getName().startsWith("tom")).map(dog -> {
DogVO dogVO = new DogVO();
dogVO.setName(dog.getName());
dogVO.setAddress(dog.getAddress());
dogVO.setOwner(dog.getOwner());
return dogVO;
}).collect(Collectors.toList()));
当一个方法中大量出现上述现象时,代码就没法读了。
还有人担心 Stream
会带来一些副作用。
参见我的另外一篇文章
Lambda
可以简化代码,但是要把握度,如果滥用 lambda 表达式,代码可读性会很差。
List<String> names = new LinkedList<>();
names.addAll(users.stream().map(user -> user.getName()).filter(userName -> userName != null).collect(Collectors.toList()));
names.addAll(users.stream().map(user -> user.getNickname()).filter(nickname -> nickname != null).collect(Collectors.toList()));
可以优化为:
List<String> names = new LinkedList<>();
names.addAll(users.stream().map(User::getName).filter(Objects::nonNull).collect(Collectors.toList()));
names.addAll(users.stream().map(User::getNickname).filter(Objects::nonNull).collect(Collectors.toList()));
对于部分复杂逻辑、对于部分需要复用的逻辑,建议封装成独立的类。
如 Stream
参数中常用的 java.util.function
包下的 Predicate
、Function
、Consumer
类和 Comparator
等。
result.addAll(dogs.stream().filter(dog -> dog.getName().startsWith("tom")).map(dog -> {
DogVO dogVO = new DogVO();
dogVO.setName(dog.getName());
dogVO.setAddress(dog.getAddress());
dogVO.setOwner(dog.getOwner());
return dogVO;
}).collect(Collectors.toList()));
改造如下:
import java.util.function.Function;
public class DogDO2VOConverter implements Function<DogDO, DogVO> {
@Override
public DogVO apply(DogDO dogDO) {
DogVO dogVO = new DogVO();
dogVO.setName(dogDO.getName());
dogVO.setAddress(dogDO.getAddress());
dogVO.setOwner(dogDO.getOwner());
return dogVO;
}
}
改造
result.addAll(dogs.stream().filter(dog -> dog.getName().startsWith("tom")).map(new DogDO2VOConverter()).collect(Collectors.toList()));
或者定义静态方法
public class DogDO2VOConverter {
public static DogVO toVo(DogDO dogDO) {
DogVO dogVO = new DogVO();
dogVO.setName(dogDO.getName());
dogVO.setAddress(dogDO.getAddress());
dogVO.setOwner(dogDO.getOwner());
return dogVO;
}
}
直接使用方法调用即可
result.addAll(dogs.stream().filter(dog -> dog.getName().startsWith("tom")).map(DogDO2VOConverter::toVo).collect(Collectors.toList()));
正如上面的代码一样
result.addAll(dogs.stream().filter(dog -> dog.getName().startsWith("tom")).map(DogDO2VOConverter::toVo).collect(Collectors.toList()));
很多人写代码时,喜欢将 Stream 操作放到方法参数中来节省一个局部变量。
我个人非常反对这种行为,这样做极大降低了代码的可读性。
我们应该将 Stream
的操作定义具有明确含义的返回值,然后再使用。
如:
List<DogVO> toms = dogs.stream().filter(dog -> dog.getName().startsWith("tom")).map(DogDO2VOConverter::toVo).collect(Collectors.toList());
result.addAll(toms);
很多人尝到了链式编程的甜头以后,总喜欢把代码写的很长。
如:
Optional.ofNullable(dogs).orElse(new ArrayList<>()).stream().filter(dog -> dog.getName().startsWith("tom")).map(DogDO2VOConverter::toVo).collect(Collectors.toList()));
但看这一句代码都很费劲,当一个函数中出现大量这种代码时,简直要吐血。
对于这种长的 Lambda 表达式写法,建议尽可能拆分出来。
List<Dog> dogs = Optional.ofNullable(dogs).orElse(new ArrayList<>());
List<String> toms = dogs.stream().filter(dog -> dog.getName().startsWith("tom")).map(DogDO2VOConverter::toVo).collect(Collectors.toList()))
然后进一步将 dogs.stream 这部分逻辑封装为子函数。
List<Dog> dogs = Optional.ofNullable(dogs).orElse(new ArrayList<>());
List<String> toms = getDogNamesStartWithTom(dogs)
这样就比较清晰易懂。
如果你发现你的项目中大量使用 Lambda,而且有很多代码的逻辑非常相似,可以考虑使用泛型封装工具类来简化代码。
下面给出两个简单的示例。
实际开发中类似先过滤后转换的代码非常多:
List<DogVO> vos = dogs.stream().map(DogDO2VOConverter::toVo).collect(Collectors.toList())
实际上这种写法习以为常,但一个方法出现多次这种写法就非常不宜读。
封装成工具类之后就比较简洁:
List<DogVO> vos = MyCollectionUtils.convert(dogs,DogDO2VOConverter::toVo);
工具类:
public class MyCollectionUtils {
public static <S, T> List<T> convert(List<S> source, Function<S, T> function) {
if (CollectionUtils.isEmpty(source)) {
return new ArrayList<>();
}
return source.stream().map(function).collect(Collectors.toList());
}
public static <S, T> List<T> convert(List<S> source, Predicate<S> predicate, Function<S, T> function) {
if (CollectionUtils.isEmpty(source)) {
return new ArrayList<>();
}
return source.stream().filter(predicate).map(function).collect(Collectors.toList());
}
}
通过将常见的样板方法封装成工具类,可以极大简化使用时的代码。
如《巧用 Spring 自动注入实现策略模式升级版》 中提到,如下案例:
定义接口
public interface Handler {
String getType();
void someThing();
}
VIP 用户实现:
import org.springframework.stereotype.Component;
@Component
public class VipHandler implements Handler{
@Override
public String getType() {
return "Vip";
}
@Override
public void someThing() {
System.out.println("Vip用户,走这里的逻辑");
}
}
普通用户实现:
@Component
public class CommonHandler implements Handler{
@Override
public String getType() {
return "Common";
}
@Override
public void someThing() {
System.out.println("普通用户,走这里的逻辑");
}
}
模拟 Service
中使用:
@Service
public class DemoService implements ApplicationContextAware {
private Map<String, List<Handler>> type2HandlersMap;
public void test(){
String type ="Vip";
for(Handler handler : type2HandlersMap.get(type)){
handler.someThing();;
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Handler> beansOfType = applicationContext.getBeansOfType(Handler.class);
beansOfType.forEach((k,v)->{
type2HandlersMap = new HashMap<>();
String type =v.getType();
type2HandlersMap.putIfAbsent(type,new ArrayList<>());
type2HandlersMap.get(type).add(v);
});
}
}
其中 setApplicationContext
里面的代码非常相似。
可以编写工具类
import org.apache.commons.collections4.MapUtils;
import org.springframework.context.ApplicationContext;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class BeanStrategyUtils {
// 构造type 到多个 bean 的映射
public static <K,B> Map<K, List<B>> buildTypeBeansMap(ApplicationContext applicationContext, Class<B> beanClass, Function<B,K> keyFunc) {
Map<K, List<B>> result = new HashMap<>();
Map<String, B> beansOfType = applicationContext.getBeansOfType(beanClass);
if(MapUtils.isEmpty(beansOfType)){
return result;
}
for(B bean : beansOfType.values()){
K type = keyFunc.apply(bean);
result.putIfAbsent(type,new ArrayList<>());
result.get(type).add(bean);
}
return result;
}
// 构造type 到单个 bean 的映射
public static <K,B> Map<K, B> buildType2BeanMap(ApplicationContext applicationContext, Class<B> beanClass, Function<B,K> keyFunc) {
Map<K, B> result = new HashMap<>();
Map<String, B> beansOfType = applicationContext.getBeansOfType(beanClass);
if(MapUtils.isEmpty(beansOfType)){
return result;
}
for(B bean : beansOfType.values()){
K type = keyFunc.apply(bean);
result.put(type,bean);
}
return result;
}
}
改造后
@Service
public class DemoService implements ApplicationContextAware {
private Map<String, List<Handler>> type2HandlersMap;
public void test(){
String type ="Vip";
for(Handler handler : type2HandlersMap.get(type)){
handler.someThing();;
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
type2HandlersMap = BeanStrategyUtils.buildTypeBeansMap(applicationContext,Handler.class, Handler::getType);
}
}
很多人可能会说,写工具方法也很花时间呢。
但是,写工具方法之后,代码重复率降低;代码更加简洁,可读性提高;后续类似逻辑都可以实现代码复用,开发效率也提高了;一举多得。
前面讲到了,可以通过封装工具类来减少 Lambda
代码的复杂性。
此外,我们还可以考虑使用一些加强包来解决这个问题。
如 StreamEx
Maven 依赖
https://mvnrepository.com/artifact/one.util/streamex
<dependency>
<groupId>one.utilgroupId>
<artifactId>streamexartifactId>
<version>0.8.0version>
dependency>
Java 8 写法
Map<Role, List<User>> role2users = users.stream().collect(Collectors.groupingBy(User::getRole));
StreamEx 写法:
Map<Role, List<User>> role2users = StreamEx.of(users).groupingBy(User::getRole);
前面的案例
List<DogVO> vos = dogs.stream().map(DogDO2VOConverter::toVo).collect(Collectors.toList())
就可以改为
List<DogVO> vos = StreamEx.of(dogs).map(DogDO2VOConverter::toVo).toList();
Maven 依赖
https://mvnrepository.com/artifact/io.vavr/vavr
<dependency>
<groupId>io.vavrgroupId>
<artifactId>vavrartifactId>
<version>1.0.0-alpha-4version>
dependency>
Java 8 中的写法:
// = ["1", "2", "3"] in Java 8
Arrays.asList(1, 2, 3)
.stream()
.map(Object::toString)
.collect(Collectors.toList())
vavr 中的写法:
// = Stream("1", "2", "3") in Vavr
Stream.of(1, 2, 3).map(Object::toString)
如果你发现某个函数里使用 Lambda 过多时(实际工作中,发现会有人一个函数里一半以上都是 lambda 表达式,非常头疼),可以考虑将部分不容易懂的 Lambda 写法改为普通写法,通常可读性会大大提高。
List<String> names = new LinkedList<>();
names.addAll(users.stream().map(User::getName).filter(Objects::nonNull).collect(Collectors.toList()));
names.addAll(users.stream().map(User::getNickname).filter(Objects::nonNull).collect(Collectors.toList()));
优化为
List<String> names = new LinkedList<>();
for(User user : users) {
String name = user.getName();
if(name!= null ){
names.add(name);
}
String nickname = user.getNickname();
if(nickname != null){
names.add(nickname);
}
}
虽然代码更长,但是更容易看懂。
还可将该部分逻辑封装为一个子函数并给一个有意义的命名。
/**
* 获取名称和昵称
*/
private List<String> getNamesAndNickNames(List<User> users) {
List<String> names = new LinkedList<>();
for (User user : users) {
String name = user.getName();
if (name != null) {
names.add(name);
}
String nickname = user.getNickname();
if (nickname != null) {
names.add(nickname);
}
}
return names;
}
使用时直接调用即可:
List<String> names = getNamesAndNickNames(users);
这样外层函数可以明确知道这部分逻辑的意图,压根不需要看这么多代码,可读性大大提高。
过犹不及,我们使用 Lambda 表达式时,一定不要忽略可读性。
Lambda 表达式没有错,错在很多人滥用 Lambda 表达式。
我们在编码过程中要注意做好权衡,掌握好度。
本文简单谈了 Lambda 表达式的利弊,给出自己的几点破解之法。
希望对大家有帮助。
当然,可能还有很多解决办法,欢迎留言补充。
希望大家能够努力做个有追求的程序员。
创作不易,如果本文对你有帮助,你的支持和鼓励,是我创作的最大动力。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有