
本文主要讲的是一个小的功能代码的优化案例,用到的知识点主要包括函数式接口(BiPredicate和Consumer)、泛型、lambda表达式、stream流。主要目的是提高代码质量,减少 “流水账” 的重复代码,提高可读性和可维护性。实现的功能是:对比两个嵌套List,求交集和差集,并对交集和差集做对应的消费处理。希望能以此抛转引玉,扩展大家使用 函数式接口的场景。
项目场景比较像俄罗斯套娃,我用例子模拟的类嵌套关系如下:
A1里有List<B1>,B1里又有List<C1>, C1里又有List<D1>
A2里有List<B2>,B2里又有List<C2>, C2里又有List<D2>
@Data
public class A1 {
private Integer id;
// 其它字段...
private List<B1> b1List;
@Data
public static class B1 {
private Integer id;
// 其它字段...
private List<C1> c1List;
@Data
public static class C1 {
private Integer id;
// 其它字段...
private List<D1> d1List;
@Data
public static class D1 {
private Integer id;
// 其它字段...
}
}
}
}
实际情况是:A1 a1是前端调用API的参数对象, A2 a2是后端获取的数据库对象,然后对比a1和a2(包括对比嵌套的List中的对象),凡是a1比a2少则新增,多则删除。
为什么会有这么变态的需求?
实际这是一个设计问题:产品和交互的设计。常规做法是界面的操作都会直接调用后端API,但这个案例有点特殊:界面上的操作不直接调用API,而是仅当点击【保存】按钮时才提交API,前端传给后端的是最终操作结果,所以后端需要对比处理界面最新结果与数据库结果,从而得到上面提到的逻辑。
咱们先实现功能,再谈代码如何优化!
如下图,交集和差集简单带一下,我们要做的是:根据集合A和集合B得到以下3个集合,然后做对应处理!
集合A和集合B的交集:5,6集合A独有:集合C集合B独有:集合D
实现例子是对象,不是简单的数字,另外因为是不同对象类型,所以我们需要明确一下不同对象类型如何“相等”,这里的“相等”是指:id相等即对象相等: p1.getId().equals(p2.getId())。
处理方式,可以使用stream方式,也可以使用传统的for循环,因为stream方式更简洁,所以推荐使用。
求集合A(aList)和集合B(bList)的交集,2个stream代替2个for循环,filter是过滤,anyMatch是有任意匹配// 循环aList, 过滤出id在bList里的对象
aList.stream().filter(p1 -> bList.stream().anyMatch(p2 -> p1.getId().equals(p2.getId())))
求aList独有的,不在bList中的,noneMatch是没有任何匹配,与anyMatch刚好相反// 循环aList, 过滤出id不在bList里的对象
aList.stream().filter(p1 -> bList.stream().noneMatch(p2 -> p1.getId().equals(p2.getId())))
求bList独有的,不在aList中的,和上例类似:// 循环bList, 过滤出id不在aList里的对象
bList.stream().filter(p1 -> aList.stream().noneMatch(p2 -> p1.getId().equals(p2.getId())))
凡是带有@FunctionalInterface注解的接口都属于函数式接口,主要作用是可以将方法当做方法的参数,并且可以被隐式转换为 lambda 表达式,所以很常用,这里主要使用BiPredicate和Consumer:
BiPredicate 两个参数的断言,返回boolean类型,原型:
boolean test(T t, U u);
这里主要用于断言两个对象是否相等,所以只需要1个BiPredicate
Consumer 消费一个对象参数,原型:
void accept(T t);
这里主要用于消费结果,因为最终有3个集合,所以需要3个Consumer
从上面的代码,我们就可以求出交集和差集,也就很容易写出具体实现的代码,但是如果不加任何技巧的话,可能写出的就会是“流水账”代码,不仅长,还带有大量的类似代码,在CV开发时一不小心就容易写错,写完自测、修改、维扩等都是很不好的体验。所以,我们优化一下代码,提高可读性和可维护性,这里定2个目标:
目标1:对现有定义好的类对象无侵入,像类A1,A2,B1,B2,C1,C2,D1,D2
目标2:通用,不能写死类型,也不能写死要对比的字段;
先定义通用方法,这里为了通用就需要使用泛型方法,因为是两个List,所以定义两个类型:T1, T2。
方法需要的参数:
最终方法定义如下:
public <T1, T2> void compareList(List<T1> aList, List<T2> bList
, BiPredicate<T1, T2> isEqualPredicate
, Consumer<List<T1>> onlyAListHaveConsumer
, Consumer<List<T2>> onlyBListHaveConsumer
, Consumer<List<T1>> bothHaveConsumer) {
}
方法没有返回值,主要是处理Consumer。
结合上面的基础代码,我们先实现一个 集合A独有的Consumer: onlyAListHaveConsumer
if (onlyAListHaveConsumer != null) {
// aList独有的,不在bList中的
List<T1> onlyAListHaveList = aList.stream().filter(p1 ->
bList.stream().noneMatch(p2 -> isEqualPredicate.test(p1, p2)))
.collect(Collectors.toList());
if (onlyAListHaveList.size() > 0) {
onlyAListHaveConsumer.accept(onlyAListHaveList);
}
}
一共就2步:过滤出onlyAListHaveList,再调用onlyAListHaveConsumer.accept(onlyAListHaveList)
同理,完整的通用封装代码如下:
public <T1, T2> void compareList(List<T1> aList, List<T2> bList
, BiPredicate<T1, T2> isEqualPredicate
, Consumer<List<T1>> onlyAListHaveConsumer
, Consumer<List<T2>> onlyBListHaveConsumer
, Consumer<List<T1>> bothHaveConsumer) {
if (onlyAListHaveConsumer != null) {
// aList独有的,不在bList中的
List<T1> onlyAListHaveList = aList.stream().filter(p1 ->
bList.stream().noneMatch(p2 -> isEqualPredicate.test(p1, p2)))
.collect(Collectors.toList());
if (onlyAListHaveList.size() > 0) {
onlyAListHaveConsumer.accept(onlyAListHaveList);
}
}
if (onlyBListHaveConsumer != null) {
// bList独有的,不在aList中的
List<T2> onlyBListHaveList = bList.stream().filter(p2 ->
aList.stream().noneMatch(p1 -> isEqualPredicate.test(p1, p2)))
.collect(Collectors.toList());
if (onlyBListHaveList.size() > 0) {
onlyBListHaveConsumer.accept(onlyBListHaveList);
}
}
if (bothHaveConsumer != null) {
// aList和bList的交集
List<T1> bothHaveList = aList.stream().filter(p1 ->
bList.stream().anyMatch(p2 -> isEqualPredicate.test(p1, p2)))
.collect(Collectors.toList());
if (bothHaveList.size() > 0) {
bothHaveConsumer.accept(bothHaveList);
}
}
}
调用的代码如下:
private void compareAndUpdateB(List<A1.B1> b1List, List<B2> b2List) {
compareList(b1List, b2List, (p1, p2) -> p1.getId().equals(p2.getId())
, onlyB1HaveList -> {
// 具体处理代码
}, onlyB2HaveList -> {
// 具体处理代码
}, bothHaveList -> {
// 具体处理代码
});
}
这里也可以将每个consumer单独提成一个方法,然后传入方法,例如定义一下方法(与Consumer方法签名相同):
public void handleOnlyB1HaveList(List<A1.B1> onlyB1HaveList) {
// 具体逻辑
}
调用时传入this::handleOnlyB1HaveList,代码如下:
private void compareAndUpdateB(List<A1.B1> b1List, List<B2> b2List) {
compareList(b1List, b2List, (p1, p2) -> p1.getId().equals(p2.getId())
, this::handleOnlyB1HaveList
, onlyB2HaveList -> {
// 具体处理代码
}, bothHaveList -> {
// 具体处理代码
});
}
函数式接口(Predicate、Consumer、Function、Supplier,以及相应扩展)、泛型、lambda表达式、stream流,这些在实际开发中非常常用,所以掌握它并灵活应用非常重要!有很多混合应用的场景,本文只以一个小的功能优化代码为例,希望能对大家有所帮助!
❤️ 博客主页:https://blog.csdn.net/scm_2008
❤️ 欢迎点赞👍 收藏 ⭐留言✏️ 如有错误敬请指正!
❤️ 本文由 天罡gg 原创,首发于 CSDN博客🙉
❤️ 停下休息的时候不要忘了别人还在奔跑,希望大家抓紧时间学习,全力奔赴更美好的生活
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.
Java的Collections.unmodifiableList和Collections.unmodifiableMap在Ruby标准API中是否有等价物? 最佳答案 使用freeze应用程序接口(interface):Preventsfurthermodificationstoobj.ARuntimeErrorwillberaisedifmodificationisattempted.Thereisnowaytounfreezeafrozenobject.SeealsoObject#frozen?.Thismethodretur