摘要:是在尝试让拥有跟类似的语法。在中使用,需要显示得将集合转成的步骤,而在中则免去了这样的步骤。中的语句只能针对常量起作用,而使用模式匹配则可以对另一个函数的返回结果起作用,功能非常抢到。
Hystrix是Netflix开源的限流、熔断降级组件,去年发现Hystrix已经不再更新了,而在github主页上将我引导到了另一个替代项目——resilience4j,这个项目是基于Java 8开发的,并且只使用了vavr库,也就是我们今天要介绍的主角。
既然要谈vavr,那么先要谈为什么要使用vavr,vavr是为了增强Java的函数式编程体验的,那么这里先介绍下Java中的函数式编程。
Java 8引入了函数式编程范式,思路是:将函数作为其他函数的参数传递,其实在Java 8之前,Java也支持类似的功能,但是需要使用接口实现多态,或者使用匿名类实现。不管是接口还是匿名类,都有很多模板代码,因此Java 8引入了Lambda表达式,正式支持函数式编程。
比方说,我们要实现一个比较器来比较两个对象的大小,在Java 8之前,只能使用下面的代码:
CompartorbyWeight = new Comparator () { public int compare(Apple a1, Apple a2) { return a1.getWeight().compareTo(a2.getWeight()); } }
上面的代码使用Lambda表达式可以写成下面这样(IDEA会提示你做代码的简化):
ComparatorbyWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
关于Lambda表达式,你需要掌握的知识点有:
Lambda表达式可以理解为是一种匿名函数:它没有名称,但是又参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表;
函数式接口就是仅仅声明了一个抽象方法的接口;
@FunctionalInterface注解对于函数式接口的作用,类似于@Override对于被重写的方法——不是必须的,但是用了有助于提升代码的可读性,因此如果你在开发中自己定义函数式接口,最好也使用这个注解修饰;
Java 8自带一些常用的函数式接口,放在java.util.function包里,包括Predicate
受限于 Java 标准库的通用性要求和二进制文件大小,Java 标准库对函数式编程的 API 支持相对比较有限。函数的声明只提供了 Function 和 BiFunction 两种,流上所支持的操作的数量也较少。基于这些原因,你也许需要vavr
来帮助你更好得使用Java 8进行函数式开发。
vavr是在尝试让Java拥有跟Scala类似的语法。vavr提供了不可变的集合框架;更好的函数式编程特性;元组。
Vavr实现了一套新的Java集合框架来匹配函数式编程范式,vavr提供的集合都是不可变的。在Java中使用Stream,需要显示得将集合转成steam的步骤,而在vavr中则免去了这样的步骤。
vavr的List是不可变的链表,在该链表对象上的操作都会生成一个新的链表对象。
使用Java 8的代码:
Arrays.asList(1, 2, 3).stream().reduce((i, j) -> i + j); IntStream.of(1, 2, 3).sum();
使用vavr实现相同的功能,则更加直接:
//io.vavr.collection.List List.of(1, 2, 3).sum();
vavr的Stream是惰性链表,元素只有在必要的时候才会参与计算,因此大部分操作都可以在常量时间内完成。
函数(Functions)Java 8提供了接受一个参数的函数式接口Function和接受两个参数的函数式接口BiFunction,vavr则提供了最多可以接受8个参数的函数式接口:Function0、Function1、Function2、Function3、Function4……Function8。
vavr还提供了更多函数式编程的特性:
组合(Composition)
在数学上,函数组合可以用两个函数形成第三个函数,例如函数f:X->Y和函数g:Y->Z可以组合成h:g(f(x)),表示X->Z。这里看个组合的例子:
public class VavrFunctionExample { @Test public void testCompose() { //使用andThen Function1plusOne = a -> a + 1; Function1 multiplyByTwo = a -> a * 2; Function1 add1AndMultiplyBy2 = plusOne.andThen(multiplyByTwo); Assert.assertEquals(6, add1AndMultiplyBy2.apply(2).intValue()); //使用compose Function1 add1AndMultiplyBy2WithCompose = multiplyByTwo.compose(plusOne); Assert.assertEquals(6, add1AndMultiplyBy2WithCompose.apply(2).intValue()); } }
Lifting
你是不是常常写这种代码:调用一个函数,判断它的返回值是否符合需求,或者需要catch所有异常以防异常情况,甚至是catch(Throwable t)。Lifting特性就是为了解决这个问题而存在的,可以在内部处理异常情况,并将异常转换成一个特殊的结果None,这样函数外部就可以用统一的模式去处理函数结果。举个例子:
public class VavrFunctionExample { @Test public void testLifting() { Function2divide = (a, b) -> a / b; Function2 > safeDivide = Function2.lift(divide); // = None Option i1 = safeDivide.apply(1, 0); Assert.assertEquals("None", i1.toString()); // = Some(2) Option i2 = safeDivide.apply(4, 2); Assert.assertEquals(2, i2.get().intValue()); } }
柯里化方法(Curring)
柯里化(Currying)指的是将原来接受多个参数的函数变成新的接受一个参数的函数的过程。对于Java来说,可以方便得提供默认值方法,这里看个例子:
public class VavrFunctionExample { @Test public void testCurried() { Function2sum = (a, b) -> a + b; Function1 add2 = sum.curried().apply(2); Assert.assertEquals(6, add2.apply(4).intValue()); } }
记忆化方法(Memorization)
这是一种缓存,某个方法只需要执行一次,后面都会返回第一次的结果;但是在实际应用中用到的地方应该不多。
public class VavrFunctionExample { @Test public void testMemorize() { Function0模式匹配hashCache = Function0.of(Math::random).memoized(); double randomValue1 = hashCache.apply(); double randomValue2 = hashCache.apply(); Assert.assertTrue(randomValue1 == randomValue1); } }
模式匹配是函数式编程语言中的概念,目前Java中还不支持这个特性,使用vavr可以用Java写模式匹配的代码。Java中的switch...case语句只能针对常量起作用,而使用模式匹配则可以对另一个函数的返回结果起作用,功能非常抢到。下面的例子分别给出了使用if、switch...case、模式匹配三个语法实现同样功能的例子,可以看出,模式匹配有助于减少代码行数。
import org.junit.Test; import static io.vavr.API.$; import static io.vavr.API.Case; import static io.vavr.API.Match; import static org.junit.Assert.assertEquals; public class VavrPatternExample { @Test public void whenIfWorksAsMatcher_thenCorrect() { int input = 3; String output; if (input == 0) { output = "zero"; } if (input == 1) { output = "one"; } if (input == 2) { output = "two"; } if (input == 3) { output = "three"; } else { output = "unknown"; } assertEquals("three", output); } @Test public void whenSwitchWorksAsMatcher_thenCorrect() { int input = 2; String output; switch (input) { case 0: output = "zero"; break; case 1: output = "one"; break; case 2: output = "two"; break; case 3: output = "three"; break; default: output = "unknown"; break; } assertEquals("two", output); } @Test public void whenMatchworks_thenCorrect() { int input = 2; String output = Match(input).of( Case($(1), "one"), Case($(2), "two"), Case($(3), "three"), Case($(), "?")); assertEquals("two", output); } }参考资料
《Java 8实战》
https://github.com/resilience4j/resilience4j
https://www.baeldung.com/vavr
https://www.vavr.io/vavr-docs/
本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/75053.html
摘要:面向对象常见的设计模式有策略模式模板方法观察者模式责任链模式以及工厂模式,使用表达式函数式编程思维有助于避免面向对象开发中的那些固定代码。 本文是一篇《Java 8实战》的阅读笔记,阅读大约需要5分钟。 有点标题党,但是这确实是我最近使用Lambda表达式的感受。设计模式是过去的一些好的经验和套路的总结,但是好的语言特性可以让开发者不去考虑这些设计模式。面向对象常见的设计模式有策略模式...
摘要:问对于程序员修炼之道你有下一步的计划吗程序员修炼之道这个项目很好,写作的过程也很愉快。而最上层的程序员则是时刻对技艺以及技术的本质着迷。这也是的一大优势。 非商业转载请注明作译者、出处,并保留本文的原始链接:http://www.ituring.com.cn/article/127453 Ben Evans是jClarity的联合创始人。其公司致力于开发可以为开发和运维团队提...
摘要:通过如下命令发布控制台,运行编写的默认程序。默认禁用,启用它需要打开并取消注释以下行。启用数据库启动应用程序的数据库,框架提供了内置的数据库的支持。当用户发出请求到,一个新的将被创建。方法为给定的获取,把这个转换成格式并返回响应。 编者注:我们发现了有趣的系列文章《30天学习30种新技术》,正在翻译,一天一篇更新,年终礼包。下面是第 30 天的内容。 今天是最后一天,我决定学习一...
摘要:本文介绍和点评上的等并发编程模型。异步更适合并发编程。同步使线程阻塞,导致等待。基本模型这是最简单的模型,创建线程来执行一个任务,完毕后销毁线程。响应式编程是一种面向数据流和变化传播的编程模式。起源于电信领域的的编程模型。 本文介绍和点评JVM上的Thread, Thread Pool, Future, Rx, async-await, Fiber, Actor等并发编程模型。本人经验...
摘要:本文介绍和点评上的等并发编程模型。异步更适合并发编程。同步使线程阻塞,导致等待。基本模型这是最简单的模型,创建线程来执行一个任务,完毕后销毁线程。响应式编程是一种面向数据流和变化传播的编程模式。起源于电信领域的的编程模型。 本文介绍和点评JVM上的Thread, Thread Pool, Future, Rx, async-await, Fiber, Actor等并发编程模型。本人经验...
阅读 1179·2021-09-03 10:44
阅读 555·2019-08-30 13:13
阅读 2783·2019-08-30 13:11
阅读 1918·2019-08-30 12:59
阅读 1011·2019-08-29 15:32
阅读 1567·2019-08-29 15:25
阅读 951·2019-08-29 12:24
阅读 1255·2019-08-27 10:58