引言

相信很多人关注 Vavr 的原因,还是因为 Hystrix 库。Hystrix 不更新了,并在 GitHub 主页上推荐了 Resilience4j,而 Vavr 作为 Resilience4j 的唯一依赖被提及。对于 Resilience4j 这个以轻依赖作为特色之一的容错库,为什么还会引用 Vavr 呢?

以下是 Resilience4j 官方原文:

Resilience4j is a lightweight fault tolerance library inspired by Netflix Hystrix, but designed for Java 8 and functional programming. Lightweight, because the library only uses Vavr, which does not have any other external library dependencies.

Resilience4j 除了轻量,另一特点是对 Java 8 函数式编程的支持,经过一番了解,Vavr 正是为了提升 Java 函数式编程体验而开发的,通过它可以帮助我们编写出更简洁、高效的函数风格代码。

限于篇幅,该系列分为上、下两篇:上篇着重回顾函数式编程的一些基础知识,以及 Vavr 总体介绍、Vavr 对元组、函数的支持,通过上篇的学习;下篇着重讲述 Vavr 中对各种值类型、增强集合、参数检查、模式匹配等特性。力求通过这两篇文章,把 Vavr 的总体特性呈现给大家,让大家对 Vavr 有个全面的认识。

简介

Vavr是 Java 8+ 函数式编程的增强库。提供了不可变数据类型和函数式控制结构,旨在让 Java 函数编程更便捷高效。特别是功能丰富的集合库,可以与Java的标准集合平滑集成。

Vavr 的读音是 /ˈweɪ.vɚ/,早期版本叫 Javaslang,由于和 Java™ 商标冲突(类似国内的 JavaEye 改名),所以把 Java 倒过来取名。

函数式编程

学习 Vavr 之前,我们先回顾下 Java 函数式编程及 Lambda (λ)  表达式的一些相关内容。

Java 8 开始,在原有面向对象、命令式编程范式的基础上,增加了函数式编程支持,其核心是行为参数化,把行为具体理解为一个程序函数(方法),即是将函数作为其它函数的参数传递,组成高阶函数。

举个例子,人(People)这个类存在一个年龄(age)属性,我们想根据每个人的年龄进行排序比较。

首先看下 Java 8 之前的做法:

Comparator comparator = new Comparator() {  @Override  public int compare(People p1, People p2) {    return Integer.compare(p1.getAge(), p2.getAge());  }};

再看 Java 8 之后的做法:

Comparator comparator    = (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge());

利用 Lambda 表达式的语法,确实少了很多模板代码,看起来更加简洁了。关于 Java 的函数式编程及 Lambda 表达式语法,有以下需要掌握的知识点:

函数式接口

函数式接口 (Functional Interface) 就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,通常会用 @FunctionalInterface 进行标注,但不是必须的。Java 8 自带了常用的函数式接口,存放在 java.util.function 包下,包括 FunctionSupplierConsumerPredicate 等,此外在其它地方也用到很多函数式接口,比如前面演示的 Comparator

Lambda 表达式

Lambda 表达式是一种匿名函数,在 Java 中,定义一个匿名函数的实质依然是函数式接口的匿名实现类,它没有名称,只有参数列表、函数主体、返回类型,可能还有一个异常列表声明。Lambda 表达式有以下重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以进行类型识别;
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号;
  • 可选的花括号:如果主体包含了一个语句,就不需要使用花括号;
  • 可选的 return 关键字:如果主体只有一个表达式返回值,则编译器会自动返回值,加了花括号需要指定表达式返回一个数值。
// 1. 不需要参数,返回值为 1() -> 1// 2. 接收一个参数(数字类型),返回值为 x + 1x -> x + 1// 3. 接受2个参数(数字),返回值为 x + y (x, y) -> x + y// 4. 接收2个int型整数,返回值为 x + y (int x, int y) -> x + y// 5. 接受一个 String 对象,并在控制台打印,不返回任何值(返回 void)  (String s) -> System.out.print(s)

副作用(Side-Effects)

如果一个操作、函数或表达式在其本地环境之外修改了某个状态变量值,则会产生副作用,也就是说,除了向操作的调用者返回一个值(主要效果)之外,还会产生可观察到的效果。

例如,一个函数产生异常,并且这个异常向上传递,就是一种影响程序的副作用,此外,异常就像一种非本地的 goto 语句,打断了正常的程序流程。具体看以下代码:

int divide(int dividend, int divisor) {    // 如果除数为0,会产生异常    return dividend / divisor;}

怎么处理这种副作用呢?在 Vavr 中,可以把它封装到一个 Try 实例,具体实现:

// = Success(result) or Failure(exception)Try safeDivide(Integer dividend, Integer divisor) {    return Try.of(() -> divide(dividend, divisor));}

这个版本的除法函数不再抛出异常,我们通过 Try 类型明确了可能的错误。

引用透明(Referential Transparency)

引用透明的概念与函数的副作用相关,且受其影响。如果程序中任意两处具有相同输入值的函数调用能够互相置换,而不影响程序的动作,那么该程序就具有引用透明性。

从以下示例可以看出,第一种实现,随机数是根据可变的外部状态生成的,所以每次调用产生的结果都不同,无法做到引用透明;第二种实现,我们给随机对象指定一个随机种子,这样保证不受外部状态的影响,达到引用透明的效果:

// 非引用透明Math.random();// 引用透明new Random(1).nextDouble();

不可变对象

不可变对象是指其状态在创建后不能修改的对象。它有以下好处:

  • 本质上是线程安全的,因此不需要同步;
  • 对于equals和hashCode来说是稳定的,因此是可靠的哈希键;
  • 不需要克隆;
  • 在未检查的协变强制转换(特定于java)中使用时表现为类型安全。

使用 Vavr

受限于 Java 标准库的通用性要求及体量大小考虑,JDK API 对函数式编程的支持比较有限,这时候可以引入 Vavr 来提供更便捷的安全集合类型、支持更多的 stream 流操作、丰富函数式接口类型……

在 Vavr 中,所有类型都是基于 Tuple, Value, λ 构建的:

图片来自 Vavr 官网

引入依赖

这里使用 Maven 项目构建,完整的 pom.xml 配置如下:

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">  4.0.0  com.example  demo-vavr  0.0.1      UTF-8    1.8    1.8    0.9.3              io.vavr      vavr      ${vavr.version}      

Java 必须是 1.8 以上版本,这是使用 Java 函数式编程的前提,另外 Vavr 使用的是 0.9.3 版本。Vavr 本身没有其它外部依赖,Jar 包大小仅有 800+K,相当轻量。

元组(Tuple)

Java 自身并没有元组的概念,元组是将固定数量的元素组合在一起,这样它们就可以作为一个整体传递,但它与数组或集合的区别是,元组能包含不同类型的对象,且是不可变的。

Vavr 提供了 Tuple1Tuple2Tuple8 等8个具体的元组类型,分别代表可存储1~8个元素的元组,并可以通过 _1_2..._8 等属性访问对应元素。

以下是创建并获取元组的示例:

// 通过 Tuple.of() 静态方法创建一个二元组Tuple2 people = Tuple.of("Bob", 18);// 获取第一个元素,名称:BobString name = people._1;// 获取第二个元素,年龄:18Integer age = people._2;

元组也提供了对元素映射处理的能力,以下两种写法效果是相同的:

// ("Hello, Bob", 9)people.map(  name -> "Hello, " + name,  age-> age / 2);// ("Hello, Bob", 9)   people.map(  (name, age) -> Tuple.of("Hello, " + name, age / 2));

此外,元组还提供了基于元素内容转换创建新的类型:

// 返回 name: Bob, age: 18String str = people.apply(  (name, age) -> "name: " + name  + ", age: " + age);

函数(Function)

Java 8 仅提供了接受一个参数的函数式接口 Function 和接受两个参数的函数式接口 BiFunction,vavr 则提供了最多可以接受8个参数的函数式接口:Function0Function1Function2...Function8。如果需要抛出受检异常的函数,可以使用 CheckedFunction{0...8} 版本。

以下是使用函数的示例:

// 声明一个接收两个参数的函数Function2 description = (name, age) -> "name: " + name  + ", age: " + age;// 返回 "name: Bob, age: 18"String str = description.apply("Bob", 18);

Vavr 函数是 Java 8 函数的增强,它提供了以下特性:

组合(Composition)

组合是将一个函数 f(x) 的结果作为另一个函数 g(y) 的参数,产生新函数 h: g(f(x)) 的操作,可以使用 andThencompose 方法实现函数组合:

Function1 plusOne = a -> a + 1;Function1 multiplyByTwo = a -> a * 2;// 以下两种写法结果一致,都是 z -> (z + 1) * 2Function1 addOneAndMultiplyByTwo1 = plusOne.andThen(multiplyByTwo);Function1 addOneAndMultiplyByTwo2 = plusOne.andThen(multiplyByTwo);

提升(Lifting)

提升是针对部分函数(partial function)的操作,如果一个函数 f(x) 的定义域是 x,另一个函数 g(y) 跟 f(x) 定义相同,只是定义域 y 是 x 的子集,就说 f(x) 是全函数(total function),g(y) 是部分函数。函数的提升会返回当前函数的全函数,返回类型为 Option,以下是一个部分函数定义:

// 当除数 0 时,将导致程序异常Function2 divide = (a, b) -> a / b;

我们再利用liftdivide提升为可以接收所有输入的全函数:

Function2> safeDivide = Function2.lift(divide);// = NoneOption i1 = safeDivide.apply(1, 0);// = Some(2)Option i2 = safeDivide.apply(4, 2);

通过以上示例可以看出,如果使用不允许的输入值调用提升后的全函数,则返回None而不是引发异常。

部分应用(Partial application)

部分应用是通过固定函数的前n个参数值,产生一个新函数,该新函数参数为原函数总参数个数减n,具体示例如下:

Function2 sum = (a, b) -> a + b;Function1 add2 = sum.apply(2);

sum函数通过部分应用,第一个参数被固定为2,并产生新的add2函数。

柯里化(Currying)

柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

Function8 的 JavaDoc说明

Function2被柯里化时,结果与部分应用没有区别,因为两者都会产生单参数的函数。但函数参数多于2个,就能明显看出柯里化的不同:

Function3 sum = (a, b, c) -> a + b + c;final Function1> add2 = sum.curried().apply(2);// = 9Integer i = add2.apply(4).apply(3);

记忆(Memoization)

函数记忆是利用缓存技术,第一次执行后,将结果缓存起来,后续从缓存返回结果。以下函数在第一次调用时,生成随机数并进行缓存,第二次调用直接从缓存返回结果,所以多次返回的随机数是同一个:

Function0 hashCache = Function0.of(Math::random).memoized();double randomValue1 = hashCache.apply();double randomValue2 = hashCache.apply();

总结

今天对 Vavr 的介绍先到这里,下篇我们将会接着介绍另外一些特性:

  • 值类型(Values)
  • 集合(Collections)
  • 参数检查(Property Checking)
  • 模式匹配(Pattern Matching)

END

历史推荐

1、三分钟快速记住 选择排序算法!

2、1.5W 字搞懂 Spring Cloud,太牛了!

3、一文带你全方位弄懂 Java8 流式操作!

4、三分钟快速记住冒泡排序算法!

公众号ID|javabaiwen

小编微信|619531440

每天分享技术干货

视频 | 电子书 | 面试题 | 开发经验

java 匿名函数_国外程序员用的火热的Vavr是什么鬼?让函数式编程更简单!相关推荐

  1. 文本编辑器_国外程序员最爱的5种文本编辑器

    文本编辑器的选择是很多初学编程者在学习编程时需要考虑的问题之一,当前IT行业应用开发平台软件较多,可供程序员选择的文本编辑器类型较多,但是一个好的文本编辑器能够提高程序工作的效率,达到事半功倍的效果. ...

  2. vue函数如何调用其他函数?_好程序员Python教程系列之递归函数与匿名函数调用...

    好程序员Python教程系列递归函数与匿名函数调用,函数是Python技术学习中重要的一个环节,深入掌握该阶段的知识内容,对于Python技术能力的提升非常有帮助,这里就针对递归函数与匿名函数两种函数 ...

  3. java static关键字_好程序员Java教程分享static关键字的理解

    好程序员Java教程分享static关键字的理解,static关键字含义可以理解为静态的. 1. 当其修饰属性时,该属性为整个类公有,所有的对象操作的都是同一个静态属性.所以调用时应该使用类名去调用, ...

  4. java求婚代码_屌丝程序员的求婚道具--内含视频

    身为程序员,求婚的方式,来点和程序相关的,是不是更有意思点呢? ###前言### 于是,从10月份,我就开始策划,要如何把这个求婚方案做完整了.我期望的求婚产生的效果如下: 浪漫 能提现我的技术本身又 ...

  5. java喷泉编码_好程序员Java教程分享使用JS实现简单喷泉效果

    原标题:好程序员Java教程分享使用JS实现简单喷泉效果 好程序员Java教程分享使用JS实现简单喷泉效果,最近,在教学生使用JS的基本操作,为了练习JS的基本作用,特地写了一个喷泉效果,代码如下: ...

  6. java属于编译_《程序员修炼之道》-读书笔记一-Java到底属于编译型语言还是解释型语言?...

    Java到底属于编译型语言还是解释型语言? 要想知道Java属于编译型语言还是解释型语言我们需要知道他们的定义和区别 定义: 编译型语言:把做好的源程序全部编译成二进制代码的可运行程序.然后,可直接运 ...

  7. java 中文乱码_好程序员Java学习路线分享如何处理中文参数

    好程序员Java学习路线分享如何处理中文参数,为什么表单中会产生中文乱码. 好程序员Java培训 产生乱码,就是因为服务器和客户端沟通的编码不一致造成的,因此解决的办法是:在客户端和服务器之间设置一个 ...

  8. java购买电视_以程序员的角度怎么购买一台「性价比高的电视」

    前俩天有小伙伴在我的文章下留言,说能否把 [国内电视机都介绍一下],今天我已在TV端开发多年的程序员的角度.谈谈已程序员的角度如何购买一台性价比高的电视. 国内大的电视机品牌介绍 长虹 -- 成立于1 ...

  9. 数字加密c语言程序_国外程序员整理的 C++ 资源大全

    喜欢的话可以收藏转发加关注 摘要:C++是在C语言的基础上开发的一种集面向对象编程.泛型编程和过程化编程于一体的编程语言.应用较为广泛,是一种静态数据类型检查的,支持多重编程的通用程序设计语言. 关于 ...

最新文章

  1. ShineTime - 带有 CSS3 闪亮特效的缩略图相册
  2. 送你了,思科设备基础配置命令大全(一),赶紧收藏......
  3. Python Django单表增删改操作
  4. java二叉树插入节点_[javaSE] 数据结构(二叉查找树-插入节点)
  5. JDK修改环境变量的作用
  6. Google第一女神李飞飞,从洗碗工蜕变成为首席科学家
  7. codeforces 122A-C语言解题报告
  8. 妈的我好像发现是哪出问题了
  9. 各科老师的语言风格一览,太真实了哈哈哈哈哈哈
  10. 开源创新的理念_开源如何解决创新问题
  11. 光动能表怎么维护_[腕表]西铁城CITIZEN AT814451E 光动能 电波表 开
  12. 代码大全旁边的一本书--感受《UNIX编程艺术》
  13. java生成随机验证图片的实现
  14. 【DL小结4】seq2seq与attention机制
  15. c++switch实现猜拳_Animate/FLASH如何制作猜拳小游戏(AS3)
  16. 「三代组装」使用Pilon对基因组进行polish
  17. 一个demo让你彻底搞懂观察者模式
  18. 聊聊c#与Python以及IronPython
  19. 考试与评价杂志考试与评价杂志社考试与评价编辑部2021年第4期目录
  20. GTX/GTH的使用心得

热门文章

  1. 【编程语言】Python-Pandas库中的交叉表
  2. mysql java 社工库_社工库源码
  3. JasperReport报表设计总结
  4. Android开发笔记(十二)测量尺寸与下拉刷新
  5. CSS3自定义浏览器滚动条样式
  6. H3C实验室Vlan的简单运用
  7. Faker 快速构造测试数据
  8. 纠错帖:Zuul Spring Cloud Gateway Linkerd性能对比
  9. 45个非常有用的Oracle查询语句(转自开源中国社区)
  10. 程序员经常遇见的9大困难你造么?