该文章已收录 【修炼内功】跃迁之路java
Lambda表达式,能够理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型。
这里,默认您已对Java8的Lambda表达式有必定了解,而且知道如何使用。程序员
Java8中引入的Lambda表达式,为编程体验及效率带来了极大的提高。express
行为参数化,是理解函数式编程的一个重要概念。简单来讲即是,一个方法接受多个不一样的行为做为参数,并在内部使用它们,完成不一样行为的能力。更为通俗的讲,行为参数化是指,定义一段代码,这段代码并不会当即执行,而是能够像普通变量/参数同样进行传递,被程序的其余部分调用。编程
咱们经过一个特别通用的筛选苹果的例子,来逐步了解如何使用Lambda表达式实现行为参数化。(若是对行为参数化已十分了解,可直接跳过本节)segmentfault
咱们须要将仓库中绿色的苹果过滤出来,对于这样的问题,大多数人来讲都是手到擒来 (step1: 面向过程)设计模式
public static List<Apple> filterGreenApples(List<Apple> apples) { List<apple> filteredApples = new LinkedList<>(); for (Apple apple: apples) { if ("green".equals(apple.getColor())) { filteredApples.add(apple); } } return filteredApples; } List<Apple> greenApples = filterGreenApples(inventory);
对于这样的需求变动,可能也不是很难闭包
public static List<Apple> filterApplesByColor(List<Apple> apples, String color) { List<apple> filteredApples = new LinkedList<>(); for (Apple apple: apples) { if (color.equals(apple.getColor())) { filteredApples.add(apple); } } return filteredApples; } List<Apple> someColorApples = filterApplesByColor(inventory, "red");
有了先前的教训,可能会学聪明一些,不会把重量直接写死到程序里,而是提供一个入参app
public static List<Apple> filterApplesByWeight(List<Apple> apples, int minWeight) { List<apple> filteredApples = new LinkedList<>(); for (Apple apple: apples) { if (apple.getWeight() > minWeight) { filteredApples.add(apple); } } return filteredApples; } List<Apple> heavyApples = filterApplesByColor(inventory, 150);
若是照此下去,程序将变得异常难于维护,每一次小的需求变动,都须要进行大范围的改动。为了不永无休止的加班,对于了解设计模式的同窗,可能会将筛选逻辑抽象出来 (step2: 面向对象)异步
public interface Predicate<Apple> { boolean test(Apple apple); }
预先定义多种筛选策略,将策略动态的传递给筛选函数jvm
public static List<Apple> filterApples(List<Apple> apples, Predicate predicate) { List<apple> filteredApples = new LinkedList<>(); for (Apple apple: apples) { if (predicate.test(apple)) { filteredApples.add(apple); } } return filteredApples; } Predicate predicate = new Predicate() { @override public boolean test(Apple apple) { return "red".equals(apple.getColor()) && apple.getWeight > 150; } }; List<Apple> satisfactoryApples = filterApples(inventory, predicate);
或者直接使用匿名类,将筛选逻辑传递给筛选函数
List<Apple> satisfactoryApples = filterApples(inventory, new Predicate() { @override public boolean test(Apple apple) { return "red".equals(apple.getColor()) && apple.getWeight > 150; } });
至此,已经能够知足大部分的需求,但对于这种十分啰嗦、被Java程序员诟病了多年的语法,在Lambda表达式出现后,便出现了一丝起色 (step3: 面向函数)
@FunctionalInterface public interface Predicate<Apple> { boolean test(Apple apple); } public List<Apple> filterApples(List<Apple> apples, Predicate<Apple> predicate) { return apples.stream.filter(predicate::test).collect(Collectors.toList()); } List<Apple> satisfactoryApples = filterApples(inventory, apple -> "red".equals(apple.getColor()) && apple.getWeight > 150);
以上示例中使用了Java8的stream及lambda表达式,关于stream及lambda表达式的具体使用方法,这里再也不赘述,重点在于解释什么是行为参数化
,示例中直接将筛选逻辑(红色且重量大于150克)的代码片断做为参数传递给了函数(确切的说是将lambda表达式做为参数传递给了函数),而这段代码片断会交由筛选函数进行执行。
Lambda表达式与匿名类很像,但本质不一样,关于Lambda表达式及匿名类的区别,会在以后的文章详细介绍
若是想让代码更为简洁明了,能够继续将筛选逻辑提取为函数,使用方法引用进行参数传递
private boolean isRedColorAndWeightGt150(Apple apple) { return "red".equals(apple.getColor()) && apple.getWeight > 150; } List<Apple> satisfactoryApples = filterApples(inventory, this::isRedColorAndWeightGt150);
至此,咱们完成了从面向过程
到面向对象
再到面向函数
的编程思惟转变,代码也更加具备语义化,不管是代码阅读仍是维护,都较以前有了很大的提高
等等,若是须要过滤颜色为黄色而且重量在180克以上的苹果,是否是还要定义一个isYellowColorAndWeightGt180
的函数出来,貌似又陷入了无穷加班的怪圈~
还有没有优化空间?可否将筛选条件抽离到单一属性,如byColor
、byMinWeight
等,以后再作与或计算传递给筛选函数?
接下来就是咱们要介绍的高阶函数
高阶函数是一个函数,它接收函数做为参数或将函数做为输出返回
- 接受至少一个函数做为参数
- 返回的结果是一个函数
以上的定义听起来可能有些绕口。结合上节示例,咱们的诉求是将苹果的颜色、重量或者其余筛选条件也抽离出来,而不是硬编码到代码中
private Predicate<apple> byColor(String color) { return (apple) -> color.equals(apple.getColor); } private Predicate<Apple> byMinWeight(int minWeight) { return (apple) -> apple.getWeight > minWeight; }
以上两个函数的返回值,均为Predicate类型的Lambda表达式,或者能够说,以上两个函数的返回值也是函数
接下来咱们定义与运算,只有传入的全部条件均知足才算最终知足
private Predicate<Apple> allMatches(Predicate<Apple> ...predicates) { return (apple) -> predicates.stream.allMatch(predicate -> predicate.test(apple)); }
以上函数,是将多个筛选逻辑作与计算,注意,该函数接收多个函数(Lambda)做为入参,并返回一个函数(Lambda),这即是高阶函数
如何使用该函数?做为苹果筛选示例的延伸,咱们能够将上一节最后一个示例代码优化以下
List<Apple> satisfactoryApples = filterApples(inventory, allMatches(byColor("red"), byMinWeight(150)));
至此,还能够抽象出anyMatches
、nonMatches
等高阶函数,组合使用
// 筛选出 颜色为红色 而且 重量在150克以上 而且 采摘时间在1周之内 而且 产地在中国、美国、加拿大任意之一的苹果 List<Apple> satisfactoryApples = filterApples( inventory, allMatches( byColor("red"), byMinWeight(150), apple -> apple.pluckingTime - currentTimeMillis() < 7L * 24 * 3600 * 1000, anyMatches(byGardens("中国"), byGardens("美国"), byGardens("加拿大") ) );
若是使用jvm包中的java.util.function.Predicate
,咱们还能够继续优化,使代码更为语义化
// 筛选出 颜色为红色 而且 重量在150克以上 而且 采摘时间在1周之内 而且 产地在中国、美国、加拿大任意之一的苹果 List<Apple> satisfactoryApples = filterApples( inventory, byColor("red") .and(byMinWeight(150)) .and(apple -> apple.pluckingTime - currentTimeMillis() < 7L * 24 * 3600 * 1000) .and(byGardens("中国").or(byGardens("美国").or(byGardens("加拿大"))) );
这里使用了Java8中的默认函数,默认函数容许你在接口interface中定义函数的默认行为,从某方面来说也能够实现类的多继承
示例中,and
/or
函数接收一个Predicate函数(Lambda表达式)做为参数,并返回一个Predicate函数(Lambda表达式),一样为高阶函数
关于默认函数的使用,会在以后的文章详细介绍
闭包(Closure),可以读取其余函数内部变量的函数
又是一个比较抽象的概念,其实在使用Lambda表达式的过程当中,常常会使用到闭包,好比
public Future<List<Apple>> filterApplesAsync() { List<Apple> inventory = getInventory(); return CompletableFuture.supplyAsync(() -> filterApples(inventory, byColor("red"))); }
在提交异步任务时,传入了内部函数(Lambda表达式),在内部函数中使用了父函数filterApplesAsync
中的局部变量inventory
,这即是闭包
。
若是该示例不够明显的话,能够参考以下示例
private Supplier<Integer> initIntIncreaser(int i) { AtomicInteger atomicInteger = new AtomicInteger(i); return () -> atomicInteger.getAndIncrement(); } Supplier<Integer> increaser = initIntIncreaser(1); System.out.println(increaser.get()); System.out.println(increaser.get()); System.out.println(increaser.get()); System.out.println(increaser.get()); //[out]: 1 //[out]: 2 //[out]: 3 //[out]: 4
initIntIncreaser
函数返回另外一个函数(内部函数),该函数(increaser
)使用了父函数initIntIncreaser
的局部变量atomicInteger
,该变量会被函数increaser
持有,而且会在调用increaser
时使用(更改)
柯里化(Currying),是把接受多个参数的函数变换成接受一个单一参数的函数。柯里化是逐步传值,逐步缩小函数的适用范围,逐步求解的过程。
如,设计一个函数,实如今延迟必定时间以后执行给定逻辑,并能够指定执行的执行器
public ScheduledFuture executeDelay(Runnable runnable, ScheduledExecutorService scheduler, long delay, TimeUnit timeunit) { return scheduler.schedule(runnable, delay, timeunit); }
目前有一批任务,须要使用执行器scheduler1
,而且均延迟5分钟执行
另外一批任务,须要使用执行器scheduler2
,而且均延迟15分钟执行
能够这样实现
executeDelay(runnable11, scheduler1, 5, TimeUnit.SECONDS); executeDelay(runnable12, scheduler1, 5, TimeUnit.SECONDS); executeDelay(runnable13, scheduler1, 5, TimeUnit.SECONDS); executeDelay(runnable21, scheduler2, 15, TimeUnit.SECONDS); executeDelay(runnable22, scheduler2, 15, TimeUnit.SECONDS); executeDelay(runnable23, scheduler2, 15, TimeUnit.SECONDS);
其实,咱们发现这里是有规律可循的,好比,使用某个执行器
在多久以后执行什么,咱们能够将executeDelay
函数进行第一次柯里化
public Function3<ScheduledFuture, Runnable, Integer, TimeUnit> executeDelayBySomeScheduler(ScheduledExecutorService scheduler) { return (runnable, delay, timeunit) -> executeDelay(runable, scheduler, delay, timeunit); } Function3<ScheduledFuture, Runnable, Integer, TimeUnit> executeWithScheduler1 = executeDelayBySomeScheduler(scheduler1); Function3<ScheduledFuture, Runnable, Integer, TimeUnit> executeWithScheduler2 = executeDelayBySomeScheduler(scheduler2); executeWithScheduler1.apply(runnable11, 5, TimeUnit.SECONDS); executeWithScheduler1.apply(runnable12, 5, TimeUnit.SECONDS); executeWithScheduler1.apply(runnable13, 5, TimeUnit.SECONDS); executeWithScheduler2.apply(runnable21, 15, TimeUnit.SECONDS); executeWithScheduler2.apply(runnable22, 15, TimeUnit.SECONDS); executeWithScheduler2.apply(runnable23, 15, TimeUnit.SECONDS);
函数executeDelay
接收4个参数,函数executeWithScheduler1
/executeWithScheduler2
接收3个参数,咱们经过executeDelayBySomeScheduler
将具体的执行器封装在了executeWithScheduler1
/executeWithScheduler2
中
进一步,咱们能够作第二次柯里化,将延迟时间也封装起来
public Function<ScheduledFuture, Runnable> executeDelayBySomeSchedulerOnDelay(ScheduledExecutorService scheduler, long delay, TimeUnit timeunit) { return (runnable) -> executeDelay(runable, scheduler, delay, timeunit); } Function<ScheduledFuture, Runnable> executeWithScheduler1After5M = executeDelayBySomeSchedulerOnDelay(scheduler1, 5, TimeUnit.SECONDS); Function<ScheduledFuture, Runnable> executeWithScheduler2After15M = executeDelayBySomeSchedulerOnDelay(scheduler2, 15, TimeUnit.SECONDS); Stream.of(runnable11, runnable12,runnable13).forEach(this::executeWithScheduler1After5M); Stream.of(runnable21, runnable22,runnable23).forEach(this::executeWithScheduler2After15M);
将具体的执行器及延迟时间封装在executeWithScheduler1After5M
/executeWithScheduler2After15M
中,调用的时候,只须要关心具体的执行逻辑便可
有时候咱们会发现,不少代码块十分类似,但又有些许不一样
好比,目前有两个接口能够查询汇率,queryExchangeRateA
及queryExchangeRateB
,咱们须要在开关exchangeRateSwitch
打开的时候使用queryExchangeRateA
查询,不然使用queryExchangeRateB
查询,同时在一个接口异常失败的时候,自动下降到另外一个接口进行查询
一样,目前有两个接口能够查询关税,queryTariffsA
及queryTariffsB
,一样地,咱们须要在开关tariffsSwitch
打开的时候使用queryTariffsA
查询,不然使用queryTariffsB
查询,同时在一个接口异常失败的时候,自动下降到另外一个接口进行查询
其实,以上两种场景,除了开关及具体的接口逻辑外,总体流程是一致的
再分析,其实接口调用的降级逻辑也是同样的
这里再也不列举如何使用抽象类的方法如解决该类问题,咱们直接使用Java8的Lambda表达式
首先,能够将降级逻辑提取为一个函数
@FunctionalInterface interface ThrowingSupplier<T> { T get() throw Exception; } /** * 1. 执行A * 2. 若是A执行异常,则执行B */ public <T> ThrowingSupplier<T> executeIfThrowing(ThrowingSupplier<T> supplierA, ThrowingSupplier<T> supplierB) throws Exception { try { return supplierA.get(); } catch(Exception e) { // dill with exception return supplierB.get(); } }
至此,咱们完成了降级的逻辑。接来下,将开关逻辑提取为一个函数
/** * 1. switcher打开,执行A * 2. switcher关闭,执行B */ public <T> T invoke(Supplier<Boolean> switcher, ThrowingSupplier<T> executeA, ThrowingSupplier<T> executeB) throws Exception { return switcher.get() ? executeIfThrowing(this::queryExchangeRateA, this::queryExchangeRateB) : executeIfThrowing(this::queryExchangeRateB, this::queryExchangeRateA); }
回到上边的两个需求,查询汇率及关税,咱们能够
/** * 查询汇率 */ val exchangeRate = invoke( exchangeRateSwitch::isOpen, this::queryExchangeRateA, this::queryExchangeRateB ) /** * 查询关税 */ val queryTariffs = invoke( tariffsSwitch::isOpen, this::queryTariffsA, this::queryTariffsB )
以上,用到了ThrowingSupplier,该点会在 《Lambda表达式里的“陷阱”》一问中详细介绍
Lambda表达式,会给以往面向对象思想的设计模式带来全新的设计思路,这部份内容但愿在设计模式系列文章中详细介绍。
关于Lambda表达式,还有很是多的内容及技巧,没法使用有限的篇幅进行介绍,同时也但愿与各位一同讨论。