1. 概述

本文将深入探讨 Vavr 中的模式匹配功能。如果你还不了解 Vavr,建议先阅读 Vavr 概览

模式匹配是 Java 原生不支持的功能,可以将其视为 switch-case 语句的增强版

Vavr 模式匹配的优势在于:它让我们免于编写堆叠的 switchif-then-else 语句,从而减少代码量,并以更符合人类阅读习惯的方式表达条件逻辑。

使用模式匹配 API 需要导入以下静态方法:

import static io.vavr.API.*;

2. 模式匹配原理

如前文所述,模式匹配可以替代传统的 switch 块:

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

或多个 if 语句:

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

    assertEquals("three", output);
}

上述代码冗长且易出错。使用模式匹配时,我们主要依赖三个核心组件:静态方法 MatchCase 和原子模式。

原子模式表示需要评估的条件,返回布尔值:

  • ✅ **$()**:通配模式,类似 switch 中的 default,处理未匹配的情况
  • ✅ **$(value)**:等值模式,直接比较输入值是否等于指定值
  • ✅ **$(predicate)**:条件模式,使用谓词函数处理输入并返回布尔值

传统方式可被更简洁的代码替代:

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

当输入无匹配时,通配模式生效:

@Test
public void whenMatchesDefault_thenCorrect() {
    int input = 5;
    String output = Match(input).of(
      Case($(1), "one"), 
      Case($(), "unknown"));

    assertEquals("unknown", output);
}

若既无匹配又无通配模式,将抛出 MatchError

@Test(expected = MatchError.class)
public void givenNoMatchAndNoDefault_whenThrows_thenCorrect() {
    int input = 5;
    Match(input).of(
      Case($(1), "one"), 
      Case($(2), "two"));
}

本节介绍了 Vavr 模式匹配的基础,后续章节将探讨处理不同场景的进阶技巧。

3. 结合 Option 的匹配

前文提到通配模式 $() 处理默认情况。另一种替代方案是将匹配结果包装在 Option 实例中:

@Test
public void whenMatchWorksWithOption_thenCorrect() {
    int i = 10;
    Option<String> s = Match(i)
      .option(Case($(0), "zero"));

    assertTrue(s.isEmpty());
    assertEquals("None", s.toString());
}

⚠️ 关于 Vavr 的 Option 详解,可参考入门文章。

4. 使用内置谓词匹配

Vavr 提供了增强可读性的内置谓词。初始示例可优化为:

@Test
public void whenMatchWorksWithPredicate_thenCorrect() {
    int i = 3;
    String s = Match(i).of(
      Case($(is(1)), "one"), 
      Case($(is(2)), "two"), 
      Case($(is(3)), "three"),
      Case($(), "?"));

    assertEquals("three", s);
}

Vavr 还支持更多谓词类型:

  • 类型检查:验证输入对象的类

    @Test
    public void givenInput_whenMatchesClass_thenCorrect() {
        Object obj=5;
        String s = Match(obj).of(
          Case($(instanceOf(String.class)), "string matched"), 
          Case($(), "not string"));
    
        assertEquals("not string", s);
    }
    
  • 空值检查:判断输入是否为 null

    @Test
    public void givenInput_whenMatchesNull_thenCorrect() {
        Object obj=5;
        String s = Match(obj).of(
          Case($(isNull()), "no value"), 
          Case($(isNotNull()), "value found"));
    
        assertEquals("value found", s);
    }
    
  • 包含检查:使用 isIn 判断输入是否在指定列表中

    @Test
    public void givenInput_whenContainsWorks_thenCorrect() {
        int i = 5;
        String s = Match(i).of(
          Case($(isIn(2, 4, 6, 8)), "Even Single Digit"), 
          Case($(isIn(1, 3, 5, 7, 9)), "Odd Single Digit"), 
          Case($(), "Out of range"));
    
        assertEquals("Odd Single Digit", s);
    }
    

谓词组合技巧

  • **allOf**:所有谓词均匹配时生效(类似 AND)

    @Test
    public void givenInput_whenMatchAllWorks_thenCorrect() {
        Integer i = null;
        String s = Match(i).of(
          Case($(allOf(isNotNull(),isIn(1,2,3,null))), "Number found"), 
          Case($(), "Not found"));
    
        assertEquals("Not found", s);
    }
    
  • **anyOf**:任一谓词匹配时生效(类似 OR)

    @Test
    public void givenInput_whenMatchesAnyOfWorks_thenCorrect() {
        Integer year = 1990;
        String s = Match(year).of(
          Case($(anyOf(isIn(1990, 1991, 1992), is(1986))), "Age match"), 
          Case($(), "No age match"));
        assertEquals("Age match", s);
    }
    
  • **noneOf**:所有谓词均不匹配时生效

    @Test
    public void givenInput_whenMatchesNoneOfWorks_thenCorrect() {
        Integer year = 1990;
        String s = Match(year).of(
          Case($(noneOf(isIn(1990, 1991, 1992), is(1986))), "Age match"), 
          Case($(), "No age match"));
    
        assertEquals("No age match", s);
    }
    

5. 自定义谓词匹配

当内置谓词无法满足需求时,可通过 Lambda 表达式创建自定义谓词:

@Test
public void whenMatchWorksWithCustomPredicate_thenCorrect() {
    int i = 3;
    String s = Match(i).of(
      Case($(n -> n == 1), "one"), 
      Case($(n -> n == 2), "two"), 
      Case($(n -> n == 3), "three"), 
      Case($(), "?"));
    assertEquals("three", s);
}

也可使用函数式接口增强灵活性(如 BiFunction):

@Test
public void givenInput_whenContainsWorks_thenCorrect2() {
    int i = 5;
    BiFunction<Integer, List<Integer>, Boolean> contains 
      = (t, u) -> u.contains(t);

    String s = Match(i).of(
      Case($(o -> contains
        .apply(i, Arrays.asList(2, 4, 6, 8))), "Even Single Digit"), 
      Case($(o -> contains
        .apply(i, Arrays.asList(1, 3, 5, 7, 9))), "Odd Single Digit"), 
      Case($(), "Out of range"));

    assertEquals("Odd Single Digit", s);
}

💡 提示:Vavr 的 FunctionN 也可用于此场景,当内置谓词不足时,自定义谓词能提供完全控制权。

6. 对象解构

对象解构是将 Java 对象分解为组件的过程。以员工信息为例:

public class Employee {
    private String name;
    private String id;
    // 标准构造器、getter/setter
}

传统 Java 方式解构:

@Test
public void givenObject_whenDecomposesJavaWay_thenCorrect() {
    Employee person = new Employee("Carl", "EMP01");

    String result = "not found";
    if (person != null && "Carl".equals(person.getName())) {
        String id = person.getId();
        result="Carl has employee id "+id;
    }

    assertEquals("Carl has employee id EMP01", result);
}

这种方式冗长且易出错。使用 Vavr 模式匹配可大幅简化:

@Test
public void givenObject_whenDecomposesVavrWay_thenCorrect() {
    Employee person = new Employee("Carl", "EMP01");

    String result = Match(person).of(
      Case(Employee($("Carl"), $()),
        (name, id) -> "Carl has employee id "+id),
      Case($(),
        () -> "not found"));
         
    assertEquals("Carl has employee id EMP01", result);
}

⚠️ 使用此功能需添加 vavr-match 依赖:获取链接

解构模式定义

需通过 @Unapply 注解定义解构逻辑:

@Patterns
class Demo {
    @Unapply
    static Tuple2<String, String> Employee(Employee Employee) {
        return Tuple.of(Employee.getName(), Employee.getId());
    }
    // 其他解构模式
}

注解处理器将生成 DemoPatterns.java,使用时需静态导入:

import static com.baeldung.vavr.DemoPatterns.*;

解构内置对象

LocalDate 为例:

@Unapply
static Tuple3<Integer, Integer, Integer> LocalDate(LocalDate date) {
    return Tuple.of(
      date.getYear(), date.getMonthValue(), date.getDayOfMonth());
}

测试用例:

@Test
public void givenObject_whenDecomposesVavrWay_thenCorrect2() {
    LocalDate date = LocalDate.of(2017, 2, 13);

    String result = Match(date).of(
      Case(LocalDate($(2016), $(3), $(13)), 
        () -> "2016-02-13"),
      Case(LocalDate($(2016), $(), $()),
        (y, m, d) -> "month " + m + " in 2016"),
      Case(LocalDate($(), $(), $()),  
        (y, m, d) -> "month " + m + " in " + y),
      Case($(), 
        () -> "(catch all)")
    );

    assertEquals("month 2 in 2017",result);
}

7. 模式匹配中的副作用

默认情况下 Match 作为表达式返回结果。若需产生副作用(如打印日志),可使用 run 辅助函数:

@Test
public void whenMatchCreatesSideEffects_thenCorrect() {
    int i = 4;
    Match(i).of(
      Case($(isIn(2, 4, 6, 8)), o -> run(this::displayEven)), 
      Case($(isIn(1, 3, 5, 7, 9)), o -> run(this::displayOdd)), 
      Case($(), o -> run(() -> {
          throw new IllegalArgumentException(String.valueOf(i));
      })));
}

辅助方法定义:

public void displayEven() {
    System.out.println("Input is even");
}

public void displayOdd() {
    System.out.println("Input is odd");
}

执行输出:

Input is even

8. 总结

本文深入探讨了 Vavr 模式匹配 API 的核心功能。通过 Vavr,我们能够用更简洁、更易读的代码替代传统的 switchif 语句,显著提升开发效率。

完整源码可参考 GitHub 项目


原始标题:A Guide to Pattern Matching in Vavr