1. 概述
本文将深入探讨 Vavr 中的模式匹配功能。如果你还不了解 Vavr,建议先阅读 Vavr 概览。
模式匹配是 Java 原生不支持的功能,可以将其视为 switch-case 语句的增强版。
Vavr 模式匹配的优势在于:它让我们免于编写堆叠的 switch
或 if-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);
}
上述代码冗长且易出错。使用模式匹配时,我们主要依赖三个核心组件:静态方法 Match
、Case
和原子模式。
原子模式表示需要评估的条件,返回布尔值:
- ✅ **
$()
**:通配模式,类似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,我们能够用更简洁、更易读的代码替代传统的 switch
和 if
语句,显著提升开发效率。
完整源码可参考 GitHub 项目。