1. 概述
在 Java 应用中,数据验证是高频需求,因此催生了大量验证库。Vavr(原 Javaslang)提供了功能完备的验证 API,它采用函数式编程风格,让数据验证变得简单直接。想快速了解 Vavr 的核心能力?可以参考这篇入门文章。
本文将深入探讨 Vavr 的验证 API,重点讲解核心方法的使用技巧。
2. Validation 接口解析
Vavr 的验证接口基于函数式编程中的应用函子(Applicative Functor)概念。它能在执行函数链时累积结果——即使部分或全部函数执行失败。
该接口的核心实现类提供了累积验证错误和有效数据的能力,支持批量处理验证结果。
3. 实战:验证用户输入
使用验证 API 处理用户输入(如 Web 层数据)非常流畅,只需创建自定义验证类,在验证数据时累积错误信息。
以登录表单的用户名和邮箱验证为例。首先添加 Maven 依赖:
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.9.0</version>
</dependency>
定义用户领域模型:
public class User {
private String name;
private String email;
// 标准构造器、getter/setter 和 toString
}
创建自定义验证器:
public class UserValidator {
private static final String NAME_PATTERN = ...;
private static final String NAME_ERROR = ...;
private static final String EMAIL_PATTERN = ...;
private static final String EMAIL_ERROR = ...;
public Validation<Seq<String>, User> validateUser(
String name, String email) {
return Validation
.combine(
validateField(name, NAME_PATTERN, NAME_ERROR),
validateField(email, EMAIL_PATTERN, EMAIL_ERROR))
.ap(User::new);
}
private Validation<String, String> validateField
(String field, String pattern, String error) {
return CharSeq.of(field)
.replaceAll(pattern, "")
.transform(seq -> seq.isEmpty()
? Validation.valid(field)
: Validation.invalid(error));
}
}
核心点在于 validateField()
方法通过正则匹配,以及 valid()
/invalid()
/combine()
的组合使用。
4. 核心方法详解
4.1 valid() 和 invalid()
- ✅
valid()
:当输入匹配正则时返回Validation.Valid
实例 - ❌
invalid()
:当输入无效时返回Validation.Invalid
实例
这种根据验证结果返回不同实例的机制,为后续处理提供了基础。
4.2 combine() 方法
combine()
是验证流程的核心,内部使用 Validation.Builder
类,支持组合最多 8 个不同的验证实例:
static <E, T1, T2> Builder<E, T1, T2> combine(
Validation<E, T1> validation1, Validation<E, T2> validation2) {
Objects.requireNonNull(validation1, "validation1 is null");
Objects.requireNonNull(validation2, "validation2 is null");
return new Builder<>(validation1, validation2);
}
Builder
类的最简形式接收两个验证实例:
final class Builder<E, T1, T2> {
private Validation<E, T1> v1;
private Validation<E, T2> v2;
// 标准构造器
public <R> Validation<Seq<E>, R> ap(Function2<T1, T2, R> f) {
return v2.ap(v1.ap(Validation.valid(f.curried())));
}
public <T3> Builder3<E, T1, T2, T3> combine(
Validation<E, T3> v3) {
return new Builder3<>(v1, v2, v3);
}
}
关键点:
ap(Function)
方法将多个验证结果合并为单个结果- 当所有结果有效时,通过指定函数(如
User::new
)生成最终值 - 结果存储在
Valid
实例中
5. 处理验证结果
获取 Validation
实例后,可通过多种方式处理结果:
UserValidator userValidator = new UserValidator();
Validation<Seq<String>, User> validation = userValidator
.validateUser("John", "john.doe@example.com");
5.1 直接实例检查
@Test
public void
givenInvalidUserParams_whenValidated_thenInvalidInstance() {
assertThat(
userValidator.validateUser(" ", "no-email"),
instanceOf(Invalid.class));
}
@Test
public void
givenValidUserParams_whenValidated_thenValidInstance() {
assertThat(
userValidator.validateUser("John", "john.doe@example.com"),
instanceOf(Valid.class));
}
⚠️ 更推荐使用 isValid()
/isInvalid()
方法替代直接实例检查。
5.2 状态检查 API
@Test
public void
givenInvalidUserParams_whenValidated_thenIsInvalidIsTrue() {
assertTrue(userValidator
.validateUser("John", "no-email")
.isInvalid());
}
@Test
public void
givenValidUserParams_whenValidated_thenIsValidMethodIsTrue() {
assertTrue(userValidator
.validateUser("John", "john.doe@example.com")
.isValid());
}
错误提取:
@Test
public void
givenInValidUserParams_withGetErrorMethod_thenGetErrorMessages() {
assertEquals(
"Name contains invalid characters, Email must be a well-formed email address",
userValidator.validateUser("John", "no-email")
.getError()
.intersperse(", ")
.fold("", String::concat));
}
有效数据提取:
@Test
public void
givenValidUserParams_withGetMethod_thenGetUserInstance() {
assertThat(userValidator.validateUser("John", "john.doe@example.com")
.get(), instanceOf(User.class));
}
5.3 toEither() API
将结果转换为 Either
接口的 Left
(错误)或 Right
(有效数据)实例:
@Test
public void
givenValidUserParams_withtoEitherMethod_thenRightInstance() {
assertThat(userValidator.validateUser("John", "john.doe@example.com")
.toEither(), instanceOf(Right.class));
}
5.4 fold() API
终极简化版:通过 fold()
一行代码处理两种结果:
@Test
public void
givenValidUserParams_withFoldMethod_thenEqualstoParamsLength() {
assertEquals(2, (int) userValidator.validateUser(" ", " ")
.fold(Seq::length, User::hashCode));
}
关键约束:
- 传入函数的返回类型必须相同
- 函数参数需匹配验证类的类型参数(如
Seq<String>
和User
)
6. 总结
Vavr 的验证 API 为数据验证提供了优雅的函数式解决方案,是传统 Java Bean Validation(如 Hibernate Validator)的强力替代方案。其核心优势在于:
- ✅ 错误累积能力
- ✅ 灵活的结果处理机制
- ✅ 简洁的链式 API