Lambda 表达式带来的复杂性的破解之道
Posted 明明如月学长
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lambda 表达式带来的复杂性的破解之道相关的知识,希望对你有一定的参考价值。
一、背景
Java 8 的 Lambda 表达式已经不再是“新特性”。
曾经很多人抵触 Lambda 表达式,现在几乎也成了标配。
实际开发中最常见的是,很多人使用 Stream
来处理集合类。
但是由于 Lambda 表达式的滥用,代码可读性会变差,那么该如何解决?
本文会讨论一下这个问题,并给出自己的几个解决办法。
二、看法
对于 Lambda 表达式或者 Stream 的看法不尽一致。
2.1 支持
(1)使用 Lambda
表达式可以减少类或者方法的创建,写起来比较简洁。
如:
import java.util.HashMap;
import java.util.Map;
public class LambdaMapDemo
public static void main(String[] args)
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++)
map.put(i, String.valueOf(i));
// 原本写法
for (Map.Entry<Integer, String> entry : map.entrySet())
System.out.println("k:" + entry.getKey() + " -> v:" + entry.getValue());
使用 Lambda 的写法
map.forEach((k, v) ->
System.out.println("k:" + k + " -> v:" + v);
);
(2) 使用 Stream
可以享受链式编程的乐趣。
(3)有些人看别人都在用,似乎有些高端,或者担心自己被淘汰也跟着大量使用。
2.2 反对
有些人对 lambda 表达式持反对意见。
(1)他们认为大量使用 lambda 表达式写出的代码不容易理解。
(2)还有的团队老人比较多,不太容易接受新的事物,不提倡使用 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()));
当一个方法中大量出现上述现象时,代码就没法读了。
(3)还有人担心 Stream
会带来一些副作用。
三、底层原理
参见我的另外一篇文章
《深入理解 Lambda 表达式》
四、建议
Lambda
可以简化代码,但是要把握度,如果滥用 lambda 表达式,代码可读性会很差。
4.1 使用方法引用
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()));
4.2 复杂代码抽取出来
对于部分复杂逻辑、对于部分需要复用的逻辑,建议封装成独立的类。
如 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()));
4.3 不要将stream 操作放在方法参数中
正如上面的代码一样
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);
4.4 Lambda 表达式不宜过长
很多人尝到了链式编程的甜头以后,总喜欢把代码写的很长。
如:
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)
这样就比较清晰易懂。
4.5 样板方法使用泛型封装
如果你发现你的项目中大量使用 Lambda,而且有很多代码的逻辑非常相似,可以考虑使用泛型封装工具类来简化代码。
下面给出两个简单的示例。
4.5.1 Stream 对象转换
实际开发中类似先过滤后转换的代码非常多:
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());
通过将常见的样板方法封装成工具类,可以极大简化使用时的代码。
4.5.2 Spring 策略模式案例
如《巧用 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 以上是关于Lambda 表达式带来的复杂性的破解之道的主要内容,如果未能解决你的问题,请参考以下文章