Vavr——一个颠覆JAVA的库
前言
Java8 在一定程度上支持了函数式编程,但标准库提供的函数式 API 不是很完备和友好。为了更好的进行函数式编程,我们就不得不借助于第三方库,而 VAVR 就是这方面的佼佼者,它可以有效减少代码量并提高代码质量。本文旨在快速的介绍一个VAVR提供的一些功能,以便我们能够快速入门VAVR。本文仅作为VAVR的入门介绍,不会完全列举API,不会深入源码分析。
环境准备
jdk:1.8
vavr:1.0.0-alpha-4
概述
Vavr提供了一些最基本类型的设计良好的表示,这些最基本的类型显然在Java中缺少或尚处于初级阶段.
在Vavr中,一切都建立在以下三个基本构建块之上:Tuple
、Value
、λ
元组,Java 缺失的结构
熟悉Scala的同学可能对于元组这一概念并不陌生,但是Java缺少元组的一般概念。元组是将固定数量的元素组合在一起,以便它们可以作为一个整体进行传递。与数组或列表不同,元组可以保存具有不同类型的对象,但它们也是不可变的。
在Vavr中元组的类型为 Tuple1、Tuple2、Tuple3 等,元组最多有8中类型的元素,并不是只能放8个元素。若要访问元组的元素,可以使用方法来访问第一个元素,访问第二个元素,依此类推。tt._1t._2
通过 Tuple
的静态工厂可以轻松创建一个元组
Tuple5<Integer, Boolean, Double, String, Character> tup = Tuple.of(1, true, 2.2, "hello", 'a');
Integer one = tup._1;
Boolean two = tup._2;
Double three = tup._3;
通过下标我们可以轻松获取到对应类型的值,而不用进行类型转换。不过值得注意的一点是元组的下标是从1开始。
更强大的函数接口
函数式编程是关于值和使用函数的值转换。Java 8 只提供了一个接受一个参数的 a 和一个接受两个参数的参数。而Vavr 提供最多 支持8 个参数的函数式接口。
Function5<Integer, Integer, Integer, Integer, Integer, Integer> f = (a, b, c, d, e) -> a + b + c + d + e;
System.out.println(f.apply(1, 2, 3, 4, 5));
我们同样可以使用Function
的静态工厂方式来创建
Function5<String, String, String, String, String, String> f2 = Function5.of(this::fiveParamsMethod);
f2.apply("a", "b", "c", "d", "e");
public String fiveParamsMethod(String a, String b, String c, String d, String e){
return a + b + c + d + e;
}
事实上,Vavr函数接口是Java 8函数接口的升级版,它还提供了一些更好玩的功能
组合
在数学中,函数合成是将一个函数与另一个函数的结果相加,从而产生第三个函数。例如,函数f :X→Y和g :Y→Z可以组合以产生映射X→Z的函数。您可以使用:h:g(f(X))。Vavr同样提供这样的能力。可以使用andThen
或者compose
Function1<Integer, Integer> plusOne = x -> x + 1;
Function1<Integer,Integer> multiplyByTwo = x -> x * 2;
Function1<Integer, Integer> f = plusOne.andThen(multiplyByTwo);
f.apply(1);
Function1<Integer, Integer> f2 = multiplyByTwo.compose(plusOne);
f2.apply(1);
提升
你可以将部分函数提升为返回Option结果的总函数。偏函数一词来源于数学。X到Y的偏函数是f: X'→Y,对于X的某个子集X '。它通过不强制f将X的每个元素映射到Y的每个元素来概括函数f: X→Y的概念。这意味着分部函数只对某些输入值起作用。如果用不允许的输入值调用函数,它通常会抛出异常。
下面的除法方法是一个只接受非零因子的部分函数。
Function2<Integer, Integer, Integer> divide = (a, b) -> a / b;
我们使用lift将divide转化为一个定义了所有输入的总函数。
Function2<Integer, Integer, Option<Integer>> safeDivide = Function2.lift(divide);
// = None
Option<Integer> i1 = safeDivide.apply(1, 0);
// = Some(2)
Option<Integer> i2 = safeDivide.apply(4, 2);
- 如果使用不允许的输入值调用函数,则提升的函数将返回None而不是引发异常。
- 如果使用允许的输入值调用函数,则提升的函数将返回Some。
例如:我们可以提升一个只允许输入正值的函数,被提升的函数捕获IllegalArgumentException并将其映射为None
@Test
public void test4(){
Function2<Integer, Integer, Option<Integer>> safeSum = Function2.lift(this::sum);
final Option<Integer> i1 = safeSum.apply(-1, -2);
final Option<Integer> i2 = safeSum.apply(1, 2);
System.out.println(i1);
System.out.println(i2);
}
private int sum(int first, int second) {
if (first < 0 || second < 0) {
throw new IllegalArgumentException("Only positive integers are allowed");
}
return first + second;
}
部分应用
部分应用允许通过固定某些值从现有函数派生新函数。您可以固定一个或多个参数,固定参数的数量定义了新函数的特性,如new arity =(original arity - fixed parameters)。这些参数是从左到右绑定原函数的
Function2<Integer, Integer, Integer> add = (a, b) -> a + b;
Function1<Integer, Integer> add2 = add.apply(2);
final Integer r = add2.apply(4);
System.out.println(r);
柯里化
柯里化是一种技术,通过为其中一个参数固定一个值来部分地应用一个函数,从而得到一个返回Function1的Function1函数。
当柯里化Function2时,结果与Function2的部分应用是无法区分的,因为两者都产生一个1-参数数量函数。
Function3<Integer, Integer, Integer, String> add = (a, b, c) -> a + 2*b + c*c+"";
final Function1<Integer, Function1<Integer, String>> apply1 = add.curried().apply(1);
final Function1<Integer, String> apply2 = apply1.apply(2);
final String apply3 = apply2.apply(3);
final Function2<Integer, Integer, String> p1 = add.apply(1);
final Function1<Integer, String> p2 = p1.apply(2);
final String apply = p2.apply(3);
我们可以看对柯里化后的函数apply的进一步调用将返回除最后一次调用之外的另一个Function1,而部分应用
却不同。
记忆化
记忆是缓存的一种形式。缓存函数只执行一次,然后从缓存返回结果。
final Function0<Long> time = Function0.of(System::currentTimeMillis).memoized();
TimeUnit.SECONDS.sleep(2);
final Function0<Long> time2 = Function0.of(System::currentTimeMillis).memoized();
System.out.println(time.get());
System.out.println(time2.get());
两次获取的结果完全一样。
值类型
在函数设置中,我们将值视为一种范式,一种无法进一步求值的表达式。在Java中,我们通过使对象的状态为final并将其称为不可变来表达这一点。
Vavr的函数值对不可变对象进行抽象。通过在实例之间共享不可变内存来添加有效的写操作。我们得到的是免费线程安全!
Option
Option是一个表示可选值的一元容器类型。Option的实例要么是Some的实例,要么是None的实例。