Vavr Core 是一个轻量但体系完整的函数式编程库。它引入不可变集合、函数式控制结构以及多种语义化容器,帮助 Java 工程师在保持运行时兼容性的前提下享受到函数式世界的表达力。
Vavr 是什么
- 不可变数据结构:提供 List、Map、Stream 等全套不可变集合,天然线程安全。
- 函数式语法糖:函数式接口覆盖 0~8 个参数,额外提供柯里化、部分应用、组合等工具。
- 容器模型:
Option、Try、Either、Validation等类型让错误处理和流程控制更加显式。 - 语法增强:属性检查(Property Checking)、模式匹配等特性补齐原生 Java 的短板。
引入依赖
Maven
<dependencies>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.4</version>
</dependency>
</dependencies>
Gradle(Groovy DSL)
dependencies {
compile "io.vavr:vavr:0.10.4"
}
Gradle 7+(推荐)
dependencies {
implementation "io.vavr:vavr:0.10.4"
}
测试特性需要额外引入
io.vavr:vavr-test,详见属性检查章节。
1. Tuples(元组)
1.1 元组概念
在 Vavr 中,Tuple1 ~ Tuple8 表示固定长度、可承载不同类型的不可变组合。
- 下标从 1 开始,通过
_1、_2……访问。 - 通常用于返回多值、承载中间状态,避免额外声明 DTO。
1.2 创建元组
final Tuple2<Integer, String> user = Tuple.of(1, "Eli auk"); Assert.equals(user._1, 1); Assert.equals(user._2, "Eli auk");
使用
Tuple.of()静态工厂创建,类型根据元素数量自动推断。
1.3 逐个映射元组组件
final Tuple2<Integer, String> mapped = user.map(
a -> a + 1,
b -> b + "1"
);
Assert.equals(mapped._1, 2);
Assert.equals(mapped._2, "Eli auk1");
1.4 使用单个映射函数
final Tuple2<Integer, String> mapped = user.map((a, b) -> Tuple.of(a + 1, b + "1")); Assert.equals(mapped._1, 2); Assert.equals(mapped._2, "Eli auk1");
当两个字段需要同时变换时可以直接返回新的
Tuple,避免多次迭代。
1.5 变换元组
final String result = user.apply((a, b) -> b.substring(2) + a); Assert.equals(result, "i auk1");
apply() 直接把各元素当作函数参数,返回任意类型的结果,非常适合拼装字符串或计算派生值。
2. Function(函数)
2.1 函数式接口
Java 8 只内建了最多两个参数的函数式接口,Vavr 将其扩展为 Function0 ~ Function8,并提供 memoized、curried、andThen 等方法,方便以 DSL 形式组织业务逻辑。
2.2 创建函数
匿名类
Function2<Integer, Integer, Integer> sum = new Function2<>() {
@Override
public Integer apply(Integer a, Integer b) {
return a + b;
}
};
Lambda
Function2<Integer, Integer, Integer> sum = (a, b) -> a + b;
静态工厂
final Function1<Integer, Integer> inc = Function1.of(a -> a + 1);
2.3 Composition(组合)
andThen:先当前再参数
Function1<Integer, Integer> plusOne = a -> a + 1; Function1<Integer, Integer> multiply = a -> a * 2; Function1<Integer, Integer> pipeline = plusOne.andThen(multiply); Assert.equals(pipeline.apply(2), 6);
compose:先参数再当前
Function1<Integer, Integer> pipeline = plusOne.compose(multiply); Assert.equals(pipeline.apply(2), 5);
记忆口诀:
andThen「先我后你」,compose「先你后我」。
2.4 Lifting(提升)
偏函数在非法入参时会抛异常。借助 lift() 可以包装为总函数:
Function2<Integer, Integer, Integer> divide = (a, b) -> a / b; Option<Integer> safeDivide = Function2.lift(divide).apply(1, 0); Assert.equals(safeDivide.isEmpty(), true);
2.5 Partial Application(部分应用)
将部分参数固定后得到新的函数,用于预置上下文。
Function2<Integer, Integer, Integer> sum = Integer::sum; Function1<Integer, Integer> add1 = sum.apply(1); Function5<Integer, Integer, Integer, Integer, Integer, Integer> sum5 = (a, b, c, d, e) -> a + b + c + d + e; Function2<Integer, Integer, Integer> add6 = sum5.apply(2, 3, 1); Assert.equals(add1.apply(2), 3); Assert.equals(add6.apply(4).apply(5), 15);
柯里化会把 FunctionN 拆成一串单参数函数:
Function1<Integer, Integer> add1Curried = sum.curried().apply(1); Function1<Integer, Function1<Integer, Function1<Integer, Function1<Integer, Integer>>>> curried = sum5.curried().apply(1); Assert.equals(add1Curried.apply(2), 3); Assert.equals(curried.apply(2).apply(3).apply(4).apply(5), 15);
差异总结:
- 语法:部分应用使用
.apply()指定多个参数,柯里化先.curried()再逐个.apply()。- 返回类型:部分应用直接得到新的
FunctionK,柯里化永远返回单参数函数链。- 灵活性:部分应用可以一次绑定多个参数,柯里化更适合链式组合。
2.6 Memoization(记忆化)
记忆化函数只计算一次,再次调用直接读取缓存;相比直接存变量,函数懒执行且可组合。
Function0<Integer> randomGenerator = Function0.of(() -> new Random().nextInt()).memoized(); Integer first = randomGenerator.apply(); Integer second = randomGenerator.apply(); Assert.equals(first, second);
常见场景:缓存昂贵计算、懒加载资源、组合多段逻辑或在单元测试中模拟依赖。
3. Value(值)
3.1 Option
封装可能为空的返回值,API 风格与 Stream-Query 的 Opp 类似,可链式 map、getOrElse,避免显式空指针判断。
3.2 Try
捕获异常并将其转化为 Success / Failure,也可参考 Stream-Query 中 Opp.ofTry 的写法,通过 recover、onFailure 等方法快速处理错误。
3.3 Lazy
惰性求值的容器,自带记忆化能力。
Lazy<Double> lazy = Lazy.of(Math::random); lazy.isEvaluated(); // false lazy.get(); // 0.123... lazy.isEvaluated(); // true Assert.equals(lazy.get(), lazy.get());
与 Supplier 相比,Lazy 只执行一次,而 Supplier 每次都会重新计算。
3.4 Either
左值通常代表错误,右值代表成功。
public Either<String, Integer> compute() {
if (RandomUtil.randomBoolean()) {
return Either.right(42);
}
return Either.left("Computation failed");
}
Either<String, Integer> value = compute().map(i -> i * 2);
if (value.isRight()) {
System.out.println("Success: " + value.get());
} else {
System.out.println("Failure: " + value.getLeft());
}
3.5 Validation
Validation 是一个应用函子,适合汇总多个字段的错误。
class Person {
final String name;
final int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
Validation<Seq<String>, Person> validatePerson(String name, int age) {
return Validation.combine(validateName(name), validateAge(age)).ap(Person::new);
}
private Validation<String, String> validateName(String name) {
return name == null || name.trim().isEmpty()
? Validation.invalid("Name cannot be empty")
: Validation.valid(name);
}
private Validation<String, Integer> validateAge(int age) {
return age < 0 || age > 150
? Validation.invalid("Age must be between 0 and 150")
: Validation.valid(age);
}
Validation<Seq<String>, Person> validPerson = validatePerson("John", 30);
Assert.equals(validPerson.get().age, 30);
Assert.equals(validPerson.get().name, "John");
Validation<Seq<String>, Person> invalidPerson = validatePerson(" ", -1);
Assert.equals(
invalidPerson.getError().asJava(),
List.of("Name cannot be empty", "Age must be between 0 and 150").asJava()
);
4. Collection(集合)
Vavr 的集合默认不可变,配合链式 API 带来更少的样板代码。
4.1 List
Optional<Integer> reduce = Stream.of(1, 2, 3).reduce(Integer::sum); int sum = IntStream.of(1, 2, 3).sum(); Number listSum = List.of(1, 2, 3).sum(); Assert.equals(listSum.intValue(), sum);
4.2 Stream
因为内建了元组,Stream 的转换能力比 JDK 版本更友好。
java.util.Map<Integer, Character> vanilla = Stream.of(1, 2, 3)
.collect(Collectors.toMap(a -> a, b -> (char) (b + 64)));
Map<Integer, Character> vavrMap = List.of(1, 2, 3).toMap(a -> a, b -> (char) (b + 64));
Assert.equals(vavrMap.get(1).get(), vanilla.get(1));
HashMap<Integer, Character> javaMap = List.of(1, 2, 3)
.toJavaMap(HashMap::new, a -> Tuple.of(a, (char) (a + 64)));
Assert.equals(javaMap.get(1), 'A');
5. Property Checking(属性检查)
需要额外依赖:
<dependency> <groupId>io.vavr</groupId> <artifactId>vavr-test</artifactId> <version>0.10.4</version> </dependency>
示例:
Arbitrary<Integer> ints = Arbitrary.integer();
Property.def("square(int) >= 0")
.forAll(ints)
.suchThat(i -> i * i >= 0)
.check()
.assertIsSatisfied();
6. Pattern Matching(模式匹配)
6.1 基础匹配
String s = Match(1).of(
Case($(1), "one"),
Case($(2), "two"),
Case($(), "?")
);
Assert.equals(s, "one");
String opt = Match(null).of(
Case($(1), "one"),
Case($(2), "two"),
Case($(), "?")
);
Assert.equals(opt, "?");
6.2 多条件匹配
int input = 5;
String output = Match(input).of(
Case($(n -> n > 0 && n < 3), "Between 1 and 2"),
Case($(n -> n > 3 && n < 6), "Between 4 and 5"),
Case($(), "Other")
);
Assert.equals(output, "Between 4 and 5");
6.3 断言匹配
String s = Match(2).of(
Case($(a -> a == 1), "one"),
Case($(a -> a == 2), "two"),
Case($(), "?")
);
Assert.equals(s, "two");
内置断言(is()、isIn()、isNotNull()、isZero() 等)可以让条件更语义化:
String opt = Match(null).of(
Case($(is(1)), "one"),
Case($(is(2)), "two"),
Case($(), "?")
);
Assert.equals(opt, "?");
通过 Vavr,可以在保持 Java 生态兼容的同时拥抱不可变模型与函数式表达式,让业务代码更简洁、更安全。

评论区
评论加载中...