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);
    }
}

关键点:

  1. ap(Function) 方法将多个验证结果合并为单个结果
  2. 当所有结果有效时,通过指定函数(如 User::new)生成最终值
  3. 结果存储在 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

完整 API 参考:官方文档
本文示例代码:GitHub 仓库


原始标题:Introduction to Vavr's Validation API