最近业务代码编写中使用到了一个函数式接口 Consumer,巧妙地解决了代码复用的问题,既解决了业务需求,代码风格又优雅,而且高度内聚。下面直接上代码案例,然后再深入介绍Java8中的几个函数式接口:Function<T, R>ConsumerPredicateSupplier。最后结合使用场景以及Java逆向移植工具Retrolambda(点这了解Retrolambda)帮助读者加深对函数式接口的理解。

Consumer案例

需求背景

因涉及系统敏感信息,案例是经过脱敏、简化后的,不影响实际理解与使用,示例代码也是根据简化后的需求从头开始编写的。

有一个订单列表的需求,不同的用户查看到的订单列表数据是不一样的,规则如下:

  • 超级管理员能查看所有订单,超级管理员能够根据不同的条件进行筛选,比如查看全部A类,比如查看单个企业,比如查看单个团队;
  • A类管理员只能查看所有A类企业的订单,也能根据单个企业、或者单个团队的条件进行筛选;B类管理员只能查看所有B类企业的订单,其他和A类管理员一样
  • 企业管理员能查看该企业下的所有订单,企业下面有很多团队,企业管理员也能根据团队进行筛选,
  • 团队管理员只能查看本团队的所有订单

所以需要根据权限将订单列表进行过滤掉,也就是说需要根据当前用户角色,设置不同的WHERE条件,传到数据库里面去查询对应的数据。

编码实现

下面列出关键代码,主要关注点在Consumer的使用,像设计、编码是否合理可以忽略。

@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderService orderService;@GetMapping("/admin")public List<AdminOrderListVO> getAdminOrderList(AdminOrderListCommand command) {List<Order> orders = orderService.getAdminOrderListByParam(command.to());return orders.stream().map(AdminOrderListVO::from).collect(Collectors.toList());}@GetMapping("/type-admin")public List<TypeAdminOrderListVO> getTypeAdminOrderList(TypeAdminOrderListCommand command) {List<Order> orders = orderService.getTypeAdminOrderListByParam(command.to());return orders.stream().map(TypeAdminOrderListVO::from).collect(Collectors.toList());}@GetMapping("/enterprise-admin")public List<EnterpriseAdminOrderListVO> getEnterpriseAdminOrderList(EnterpriseAdminOrderListCommand command) {List<Order> orders = orderService.getEnterpriseOrderListByParam(command.to());return orders.stream().map(EnterpriseAdminOrderListVO::from).collect(Collectors.toList());}
}
public class OrderService {@Autowiredprivate OrderMapper orderMapper;public List<Order> getAdminOrderListByParam(AdminOrderListParam adminParam) {OrderService.fillCommonCondition(adminParam::setEnterpriseType, adminParam::setEnterpriseId, adminParam::setTeamId);return orderMapper.findAdminOrderListByParam(adminParam);}public List<Order> getTypeAdminOrderListByParam(TypeAdminOrderListParam typeAdminParam) {OrderService.fillCommonCondition(typeAdminParam::setEnterpriseType,typeAdminParam::setEnterpriseId, typeAdminParam::setTeamId);return orderMapper.findTypeAdminOrderListByParam(typeAdminParam);}public List<Order> getEnterpriseOrderListByParam(EnterpriseAdminOrderListParam enterpriseAdminParam) {OrderService.fillCommonCondition(enterpriseAdminParam::setEnterpriseType,enterpriseAdminParam::setEnterpriseId, enterpriseAdminParam::setTeamId);return orderMapper.findEnterpriseAdminOrderListByParam(enterpriseAdminParam);}public static void fillCommonCondition(Consumer<String> setEnterpriseType,Consumer<Integer> setEnterpriseId, Consumer<Long> setTeamId) {if (setEnterpriseType != null) {setEnterpriseType.accept(CurrentUserUtil.currentEnterpriseType());}if (setEnterpriseId != null) {setEnterpriseId.accept(CurrentUserUtil.currentEnterpriseId());}if (setTeamId != null) {setTeamId.accept(CurrentUserUtil.currentTeamId());}}
}

分析

上面列出了Controller和Service,Controller有三个订单列表的接口,他们有不同的参数对象,接口逻辑都是先将参数对象转成Service的入参对象,调用Service的逻辑,最后将Service返回数据转成对应VO。重点是在Service里面,三个Service方法都共同调用了fillCommonCondition方法,这个方法的功能就是:动态地向不同对象中设置属性值,实现原理就是根据传进来的Consumer函数式接口,执行下传进来的方法,并且是带一个参数的,相当于动态调用了不同对象的Set方法,把当前用户某些属性设置到对象中。

不同的Consumer参数类型是可以不一样的,但是同一个字段,在不同对象中类型需要一样。其实fillCommonCondition方法不仅适用在订单列表,其实整个系统的权限控制都是这个逻辑,这种写法适用于所有需要权限控制的场景,不限对象类型,实现了代码高度复用,不然需要在每个接口手动调用当前参数对象的SET方法来设置值。

在上面例子中,我理解的就是将set方法作为参数传到另一个方法里面,然后去执行传进来的set方法,其他的函数式接口也是类似,只是根据方法参数和返回值分了类。以前实现动态方法调用基本就是使用反射,用起来比较繁琐,而且代码很僵硬。使用了函数式接口代码十分简洁,由此想深入理解下Java8中的几个函数式接口。

Function<T, R>

Function<T, R>首先是一个接口,里面有一个抽象方法,三个默认实现的方法,主要是R apply(T t)方法,实现Function接口就需要实现apply方法,比如x -> 2 * x就是一个函数式接口,可以转换成JDK1.7内部类,重写了apply方法的形式,代码如下

Function<Integer, Integer> lambda = x -> 2 * x;Function<Integer, Integer> function = new Function<Integer, Integer>() {@Overridepublic Integer apply(Integer x) {return 2 * x;}
};

jdk源码里面的一个方法

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

这是java.util.stream.Stream的map方法,参数就是一个Function接口。在上面Consumer的案例中,最后一步转成VO的时候,使用了Stream中的map方法,传进去了from的静态方法。效果就是将List转化成List,对每一个Order,都会调用传进去的from方法。

下面通过两个Function<T, R>的例子来演示不同的调用方式,第一个案例是实际传的Function是有参数的,第二个时没有参数的。

案例一

public class FunctionTest {public static void main(String[] args) {FunctionTest functionTest = new FunctionTest();String s = functionTest.doFunction(functionTest::hasOneParam, "s");Integer integer = functionTest.doFunction(functionTest::increase, 6);System.out.println(s);System.out.println(integer);}public <T, R> R doFunction(Function<T, R> function, T param) {return function.apply(param);}public <T> String hasOneParam(T param) {return param.toString();}public Integer increase(Integer i) {return i + 1;}
}//运行结果
s
7
Process finished with exit code 0

doFunction就是执行传进来的方法,而且该方法的参数也是传进来的,相当于动态调用了一遍方法。我们通过Retrolambda工具将上面的代码编译成JDK6的Class文件,然后用IDEA反编译打开看下里面的内容。

上面FunctionTest类编译以后的是三个文件,因为有两个Lambda表达式,在JDK6中是使用内部类来实现的,而内部类编译后是单独Class文件。

打开看文件内容

每个Lambda表达式对应一个类,这个类实现了Function接口,FunctionTestKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲2这个类是静态方法当做Func…Lambda$2这个类的实例,这个实例是通过调用工厂方法得到的,doFunction中就是调用具体的实现类的apply方法,参数也传到具体方法里面去,这样就实现了动态方法调用。

FunctionTestKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲1这个类比FunctionTe…Lambda$2多了一个属性,这个属性是被调用方法所属的类,通过工厂方法传进来,因为实例方法的调用必须指明是哪个实例,静态方法可以直接通过类名来调用。

案例二

public class FunctionTest2 {public static void main(String[] args) {FunctionTest2 functionTest2 = new FunctionTest2();String s = functionTest2.doFunction(FunctionTest2::hasNoParam, functionTest2);System.out.println(s);}public <T, R> R doFunction(Function<T, R> function, T param) {return function.apply(param);}public String hasNoParam() {return "A";}
}
//运行结果
A
Process finished with exit code 0

编译后的内容

这个案例和案例一的区别就是被动态调用的方法是没有参数的,apply方法是必须要传一个参数,所以这里的参数变成了被动态调用方法所属的实例。从代码上看,区别就是FunctionTest2KaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲1的apply方法参数是被转成…Lambda$1中apply方法的参数是原封不动地传到hasOneParam的形参里面去。

其他函数式接口

案例二的写法有点类似Supplier的功能,没有参数但是提供一个返回值。如果使用参数,不使用Function的返回值,就变成了Consumer,所以其他的一些函数式接口原理都是类似的,有些变换了形式,有些通过继承、添加默认实现方法扩展了功能,像下面这些:

  • BiFunction<T, U, R> 传两个参数,带一个返回值
  • Predicate 传一个参数,返回一个布尔类型的值
  • Supplier 没有参数,直接获取返回值
  • BiPredicate<T, U> 传两个参数,返回一个布尔类型的值

自定义函数式接口

JDK的java.util.function包提供了很多函数式接口,如果不满足业务需求,可以自定义函数式接口,比如下面是一个函数式接口,接收三个参数,带一个返回值

@FunctionalInterface
public interface MyFunction<T, V, R, P> {R apply(T t, V v, P p);
}

也可以将一些参数设置成固定的类型,如String,Integer或者具体对象类型,如 R apply(T t, String v, List lists)。

函数式接口的使用还算简单的,就是把方法当做参数传到方法里面,只是以前我们是传值类型的参数。函数式接口里面还可以有逻辑,甚至可以函数式接口嵌套或者叠加使用,可以根据自己想象力和业务需求玩出更骚、更花的一些操作,总的来说函数式接口确实方便了编码,可以先学起来,多实践,慢慢理解。

Java函数式接口讲解与应用相关推荐

  1. java多线程查询_利用Java函数式接口处理多线程查询

    Java函数式接口 有且只有一个抽象方法的接口被称为函数式接口. @FunctionalInterface注解: 该注解可用于一个接口的定义上, 一旦使用该注解来定义接口, 编译器将会强制检查该接口是 ...

  2. JAVA 函数式接口存在

    Java函数式接口不仅有必要存在,个人觉得,函数式接口是Java里唯一有必要存在的接口! 为什么?这要从接口存在的意义说起. 假定你的老师给你这么一道题:用Java写一个冒泡排序,必须支持对任意类型的 ...

  3. Java函数式接口看这一篇就够了

    目录: 1.函数式接口的基本概念和格式 2.函数式编程 3.函数式接口作为方法的参数和方法的返回值 4.常用函数式接口 1.函数式接口的基本概念和格式 1.函数式接口的基本概念: 函数式接口在Java ...

  4. java 函数式接口与Lambda表达式

    目录 函数式接口 函数式接口简介 什么是 @FunctionalInterface 内置的函数式接口 Stream和Lambda常用的函数式接口 函数式接口的使用 Lambda表达式 Lambda来源 ...

  5. java函数式接口意义与场景

    前言 想到记录下这篇主要是两个原因,1. 虽然自己很早就接触了函数式接口,但是基本没有深入探究过使用场景.2. 工作中接触了越来越多场景后,感觉对函数式接口有更多使用需求,能很好的美化自己代码(少写几 ...

  6. java 函数式接口与lambda表达式的关系

    函数式接口与lambda表达式的关系 在java中,lambda表达式与函数式接口是不可分割的,都是结合起来使用的. 对于函数式接口,我们可以理解为只有一个抽象方法的接口,除此之外它和别的接口相比并没 ...

  7. Java函数式接口--抽象方法接口

    1 函数式接口 函数式接口在Java中是指: 有且仅有一个抽象方法的接口 函数式接口, 即适用于函数式编程场景的接口; 而Java中函数式编程体现就是Lambda, 所以函数式接口就是可以适用于Lam ...

  8. 关于学习java函数式接口Function中的apply方法的一些感悟

    起因是这样的,学习函数式编程的时候学到了Function接口,对于其中的apply方法感到不解,下面贴上我的不解代码 在这里插public class Function接口 {public stati ...

  9. Java函数式接口前世今生全面解析包教包会

    函数式接口 一句话总结:函数式接口的作用是让函数成为函数的参数. ​ 如果你直接去搜"函数式接口",可能会得到一句"有用的废话":只有一个抽象方法的接口就叫函数 ...

最新文章

  1. 现在转行学习UI设计好不好就业
  2. 不同表_不同电脑剪视频的速度对比表20200617更新;附素材和方法
  3. 故障模块名称kernelbase.dll_故障码都看不懂,你还修啥车?
  4. linux原理 培训,Linux容器技术原理和使用
  5. SDN精华问答 | 为什么SDN要花这么长时间才被采用?
  6. 1.7编程基础之字符串 06 合法 C 标识符 python
  7. Python 实现单例模式
  8. linux下不同tomcat使用不同的jdk版本
  9. DCMTK3.6.0(MD支持库)安装说明
  10. 微分销机制设计_免费快速搭建微信分销商城_OctShop源码
  11. 求最大值 最小值 下标 及格率 c语言,输入某班的C语言成绩,计算输出其及格率...
  12. php codeigniter 教程,CodeIgniter操作教程
  13. css 排版_Web排版的未来:CSS字体4级
  14. html制作网易云音乐登录系统,JavaScript实现[网易云音乐Web站登录窗口]拖拽功能...
  15. 软件性能测试 容量测试,性能测试容量计算方法
  16. matlab整除方式
  17. free pascal
  18. 前后端分离及项目开发流程
  19. 分布式存储解决方案zData
  20. python基础进阶之堆糖头像爬虫

热门文章

  1. 【汽车电子】浅谈车载系统QNX
  2. Android自定义View实现打钩签到动画
  3. 旅游指南之十九----大理
  4. java.util.sortedmap_Java SortedMap 接口
  5. 学1妹的软件测试转行之路
  6. 程序员直击养老院现场,养老院已成为年轻人向往自由的乌托邦
  7. 四、nginx正向代理
  8. 【C++】优先级队列
  9. java基于ssm蛋糕店蛋糕商城蛋糕系统网站源码
  10. 帮我出一份正规的可以促使眉毛生长的办法,以达到浓眉的目的