1. 概述

本文将深入探讨 Vavr 库的核心价值、使用场景及实践方法。Vavr 是 Java 8+ 的函数式编程库,提供不可变数据类型和函数式控制结构

1.1 Maven 依赖

使用 Vavr 前需添加依赖:

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.9.0</version>
</dependency>

建议始终使用最新版本,可通过 此链接 获取。

2. Option

Option 的核心目标是利用 Java 类型系统消除代码中的 null 检查

Vavr 的 Option 类似 Java 8 的 Optional,但实现了 SerializableIterable,且 API 更丰富。在 Java 中,任何对象引用都可能为 null,使用前需通过 if 语句检查:

@Test
public void givenValue_whenNullCheckNeeded_thenCorrect() {
    Object possibleNullObj = null;
    if (possibleNullObj == null) {
        possibleNullObj = "someDefaultValue";
    }
    assertNotNull(possibleNullObj);
}

若无检查,应用可能因 NullPointerException 崩溃:

@Test(expected = NullPointerException.class)
public void givenValue_whenNullCheckNeeded_thenCorrect2() {
    Object possibleNullObj = null;
    assertEquals("somevalue", possibleNullObj.toString());
}

但检查会使代码 冗长且可读性差,尤其当 if 语句多层嵌套时。Option 通过完全消除 null 解决此问题:

  • null 值转为 None 实例
  • null 值转为 Some 实例
@Test
public void givenValue_whenCreatesOption_thenCorrect() {
    Option<Object> noneOption = Option.of(null);
    Option<Object> someOption = Option.of("val");

    assertEquals("None", noneOption.toString());
    assertEquals("Some(val)", someOption.toString());
}

最佳实践:将对象包装在 Option 中,而非直接使用。OptiontoString() 会返回有意义的值,无需 null 检查。

默认值处理

传统方式需显式检查:

@Test
public void givenNull_whenCreatesOption_thenCorrect() {
    String name = null;
    Option<String> nameOption = Option.of(name);
   
    assertEquals("baeldung", nameOption.getOrElse("baeldung"));
}

null 值同样适用:

@Test
public void givenNonNull_whenCreatesOption_thenCorrect() {
    String name = "baeldung";
    Option<String> nameOption = Option.of(name);

    assertEquals("baeldung", nameOption.getOrElse("notbaeldung"));
}

⚠️ 优势:无需 null 检查,一行代码即可获取值或返回默认值。

3. 元组 (Tuple)

Java 没有原生元组结构。元组在函数式编程中很常见,不可变且能以类型安全的方式容纳多个不同类型的对象

Vavr 为 Java 8 引入元组,支持 Tuple1Tuple8(最多 8 个元素)。通过 tuple._n 访问元素(n 从 1 开始):

public void whenCreatesTuple_thenCorrect1() {
    Tuple2<String, Integer> java8 = Tuple.of("Java", 8);
    String element1 = java8._1;
    int element2 = java8._2();

    assertEquals("Java", element1);
    assertEquals(8, element2);
}

注意:元组索引从 1 开始(非数组式 0 基)。元素类型需在声明时指定:

@Test
public void whenCreatesTuple_thenCorrect2() {
    Tuple3<String, Integer, Double> java8 = Tuple.of("Java", 8, 1.8);
    String element1 = java8._1;
    int element2 = java8._2;
    double element3 = java8._3;
        
    assertEquals("Java", element1);
    assertEquals(8, element2);
    assertEquals(1.8, element3, 0.1);
}

典型场景:从方法返回多个对象时,元组比创建临时类更简洁。

4. Try

Vavr 中,**Try 是可能抛出异常的计算容器**。

正如 Option 包装可空对象避免 null 检查,Try 包装计算避免显式 try-catch 块。例如:

@Test(expected = ArithmeticException.class)
public void givenBadCode_whenThrowsException_thenCorrect() {
    int i = 1 / 0;
}

try-catch 时应用崩溃。用 Vavr 包装后:

@Test
public void givenBadCode_whenTryHandles_thenCorrect() {
    Try<Integer> result = Try.of(() -> 1 / 0);

    assertTrue(result.isFailure());
}

可在代码任意位置检查计算状态。其他操作示例:

  • 返回默认值:

    @Test
    public void givenBadCode_whenTryHandles_thenCorrect2() {
        Try<Integer> computation = Try.of(() -> 1 / 0);
        int errorSentinel = computation.getOrElse(-1);
    
        assertEquals(-1, errorSentinel);
    }
    
  • 抛出自定义异常:

    @Test(expected = ArithmeticException.class)
    public void givenBadCode_whenTryHandles_thenCorrect3() {
        Try<Integer> result = Try.of(() -> 1 / 0);
        result.getOrElseThrow(ArithmeticException::new);
    }
    

⚠️ 核心价值:通过 Try 完全掌控计算后的行为。

5. 函数式接口

Java 8 内置函数式接口,但仅支持:

  • 单参数函数(Function):

    @Test
    public void givenJava8Function_whenWorks_thenCorrect() {
        Function<Integer, Integer> square = (num) -> num * num;
        int result = square.apply(2);
    
        assertEquals(4, result);
    }
    
  • 双参数函数(BiFunction):

    @Test
    public void givenJava8BiFunction_whenWorks_thenCorrect() {
        BiFunction<Integer, Integer, Integer> sum = 
          (num1, num2) -> num1 + num2;
        int result = sum.apply(5, 7);
    
        assertEquals(12, result);
    }
    

Vavr 扩展至 最多 8 个参数,并新增记忆化、组合和柯里化等 API。接口按参数数命名:Function0Function1 等。示例:

@Test
public void givenVavrFunction_whenWorks_thenCorrect() {
    Function1<Integer, Integer> square = (num) -> num * num;
    int result = square.apply(2);

    assertEquals(4, result);
}
@Test
public void givenVavrBiFunction_whenWorks_thenCorrect() {
    Function2<Integer, Integer, Integer> sum = 
      (num1, num2) -> num1 + num2;
    int result = sum.apply(5, 7);

    assertEquals(12, result);
}

无参数时,Java 8 用 Supplier,Vavr 用 Function0

@Test
public void whenCreatesFunction_thenCorrect0() {
    Function0<String> getClazzName = () -> this.getClass().getName();
    String clazzName = getClazzName.apply();

    assertEquals("com.baeldung.vavr.VavrTest", clazzName);
}

五参数函数使用 Function5

@Test
public void whenCreatesFunction_thenCorrect5() {
    Function5<String, String, String, String, String, String> concat = 
      (a, b, c, d, e) -> a + b + c + d + e;
    String finalString = concat.apply(
      "Hello ", "world", "! ", "Learn ", "Vavr");

    assertEquals("Hello world! Learn Vavr", finalString);
}

技巧:通过 FunctionN.of 从方法引用创建函数。例如:

public int sum(int a, int b) {
    return a + b;
}
@Test
public void whenCreatesFunctionFromMethodRef_thenCorrect() {
    Function2<Integer, Integer, Integer> sum = Function2.of(this::sum);
    int summed = sum.apply(5, 6);

    assertEquals(11, summed);
}

6. 集合 (Collections)

Vavr 团队设计了 满足函数式编程要求的全新集合 API(持久化、不可变)。

Java 集合是可变的,易引发程序故障(尤其在并发场景)。Collection 接口包含此类方法:

interface Collection<E> {
    void clear(); // 产生副作用,无返回值
}

为解决此类问题引入了 ConcurrentHashMap,但 边际效益为零且降低性能

不可变性天然保证线程安全,无需额外类库。Java 中现有不可变方案(如 Collections.unmodifiableList)会抛出异常:

@Test(expected = UnsupportedOperationException.class)
public void whenImmutableCollectionThrows_thenCorrect() {
    java.util.List<String> wordList = Arrays.asList("abracadabra");
    java.util.List<String> list = Collections.unmodifiableList(wordList);
    list.add("boom");
}

Vavr 集合完全规避上述问题。创建列表示例:

@Test
public void whenCreatesVavrList_thenCorrect() {
    List<Integer> intList = List.of(1, 2, 3);

    assertEquals(3, intList.length());
    assertEquals(new Integer(1), intList.get(0));
    assertEquals(new Integer(2), intList.get(1));
    assertEquals(new Integer(3), intList.get(2));
}

支持直接计算:

@Test
public void whenSumsVavrList_thenCorrect() {
    int sum = List.of(1, 2, 3).sum().intValue();

    assertEquals(6, sum);
}

核心优势

  • 不可变性
  • 消除 void 返回类型和副作用 API
  • 更丰富的元素操作函数
  • 代码更简洁健壮

Vavr 集合覆盖 Java 集合框架大部分类,完整实现所有功能。本文不展开详述。

7. 验证 (Validation)

Vavr 将函数式编程中的 应用函子 (Applicative Functor) 概念引入 Java。简言之,应用函子允许在执行操作序列时累积结果

vavr.control.Validation 类支持错误累积。通常程序遇错即终止,但 Validation 会继续处理并批量返回错误。

假设通过 nameage 注册用户,需先收集所有输入再决定创建 Person 或返回错误列表。Person 类如下:

public class Person {
    private String name;
    private int age;

    // 标准构造器、getter/setter 和 toString
}

创建 PersonValidator 类,每个字段一个验证方法,组合结果到 Validation 实例:

class PersonValidator {
    String NAME_ERR = "Invalid characters in name: ";
    String AGE_ERR = "Age must be at least 0";

    public 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) {
        String invalidChars = name.replaceAll("[a-zA-Z ]", "");
        return invalidChars.isEmpty() ? 
          Validation.valid(name) 
            : Validation.invalid(NAME_ERR + invalidChars);
    }

    private Validation<String, Integer> validateAge(int age) {
        return age < 0 ? Validation.invalid(AGE_ERR)
          : Validation.valid(age);
    }
}

规则:age ≥ 0,name 无特殊字符:

@Test
public void whenValidationWorks_thenCorrect() {
    PersonValidator personValidator = new PersonValidator();

    Validation<List<String>, Person> valid = 
      personValidator.validatePerson("John Doe", 30);

    Validation<List<String>, Person> invalid = 
      personValidator.validatePerson("John? Doe!4", -1);

    assertEquals(
      "Valid(Person [name=John Doe, age=30])", 
        valid.toString());

    assertEquals(
      "Invalid(List(Invalid characters in name: ?!4, 
        Age must be at least 0))", 
          invalid.toString());
}

关键点

  • 有效值在 Validation.Valid 实例中
  • 错误列表在 Validation.Invalid 实例中
  • 验证方法必须返回二者之一

8. 延迟 (Lazy)

Lazy延迟计算值的容器:计算推迟至需要结果时执行,且结果会被缓存(记忆化),后续调用直接返回缓存值:

@Test
public void givenFunction_whenEvaluatesWithLazy_thenCorrect() {
    Lazy<Double> lazy = Lazy.of(Math::random);
    assertFalse(lazy.isEvaluated());
        
    double val1 = lazy.get();
    assertTrue(lazy.isEvaluated());
        
    double val2 = lazy.get();
    assertEquals(val1, val2, 0.1);
}
  • 第 2 行:函数未执行(isEvaluated() 返回 false
  • 第 4 行:调用 get() 触发计算,isEvaluated() 变为 true
  • 第 7 行:再次调用 get() 返回缓存值(非重新计算)

⚠️ 记忆化验证:若函数重新执行,val1val2 应不同(Math.random),但实际相等,证明缓存生效。

9. 模式匹配 (Pattern Matching)

模式匹配是函数式编程语言的原生特性,Java 尚未支持。传统方案需多层 ifswitch

@Test
public void whenIfWorksAsMatcher_thenCorrect() {
    int input = 3;
    String output;
    if (input == 0) {
        output = "zero";
    }
    if (input == 1) {
        output = "one";
    }
    if (input == 2) {
        output = "two";
    }
    if (input == 3) {
        output = "three";
    }
    else {
        output = "unknown";
    }

    assertEquals("three", output);
}

switch(平均 3 行/条件):

@Test
public void whenSwitchWorksAsMatcher_thenCorrect() {
    int input = 2;
    String output;
    switch (input) {
    case 0:
        output = "zero";
        break;
    case 1:
        output = "one";
        break;
    case 2:
        output = "two";
        break;
    case 3:
        output = "three";
        break;
    default:
        output = "unknown";
        break;
    }

    assertEquals("two", output);
}

痛点:代码冗长,易漏 break 导致隐蔽错误。

Vavr 用 Match 替代整个 switch 块,Case 方法替代条件,原子模式 $() 替代条件表达式:

@Test
public void whenMatchworks_thenCorrect() {
    int input = 2;
    String output = Match(input).of(
      Case($(1), "one"), 
      Case($(2), "two"), 
      Case($(3), "three"),
      Case($(), "?"));
 
    assertEquals("two", output);
}

优势:平均 1 行/条件,代码紧凑。API 支持更复杂场景,如用谓词替换原子表达式(解析控制台命令):

Match(arg).of(
    Case($(isIn("-h", "--help")), o -> run(this::displayHelp)),
    Case($(isIn("-v", "--version")), o -> run(this::displayVersion)),
    Case($(), o -> run(() -> {
        throw new IllegalArgumentException(arg);
    }))
);

谓词、多条件和副作用处理将在后续文章详解。

10. 总结

本文介绍了 Vavr——Java 8 流行的函数式编程库,覆盖了可快速提升代码质量的核心特性。完整示例代码见 GitHub 项目


原始标题:Introduction to Vavr

» 下一篇: JMX基础介绍