1. 概述
本文将探讨一种替代标准 try-catch 块的函数式异常处理方案。我们将使用 Vavr 库中的 Try
类,通过将错误处理嵌入正常程序流程,构建更流畅且可读性更强的 API。
如果想深入了解 Vavr,可参考这篇文章。
2. 传统异常处理方式
假设我们有一个包含 call()
方法的简单接口,成功时返回 Response
,失败时抛出受检异常 ClientException
:
public interface HttpClient {
Response call() throws ClientException;
}
Response
是仅包含 id
字段的简单类:
public class Response {
public final String id;
public Response(String id) {
this.id = id;
}
}
当服务调用 HttpClient
时,需要用标准 try-catch 处理受检异常:
public Response getResponse() {
try {
return httpClient.call();
} catch (ClientException e) {
return null;
}
}
这种方式的痛点:
- ✅ 每个抛出受检异常的方法都会打断程序流程
- ❌ 代码中充斥大量 try-catch 块,可读性差
- ⚠️ 返回
null
可能导致后续 NPE 风险
理想情况下,我们需要一个能封装结果状态(成功/失败)的特殊类,根据状态链式处理后续操作。
3. 使用 Try 处理异常
Vavr 提供的 Try
容器表示可能抛出异常或成功完成的计算。将操作封装在 Try
对象中后,结果只能是 Success
或 Failure
,我们可以据此执行后续操作。
用 Try
重写之前的 getResponse()
方法:
public class VavrTry {
private HttpClient httpClient;
public Try<Response> getResponse() {
return Try.of(httpClient::call);
}
// 标准构造器
}
关键点在于返回类型 Try<Response>
。调用此类方法时必须显式处理两种可能结果:
- ✅ 成功时返回
Success<Response>
- ❌ 失败时返回
Failure<ClientException>
3.1. 处理成功场景
当 httpClient
成功返回结果时,测试用例如下:
@Test
public void givenHttpClient_whenMakeACall_shouldReturnSuccess() {
// given
Integer defaultChainedResult = 1;
String id = "a";
HttpClient httpClient = () -> new Response(id);
// when
Try<Response> response = new VavrTry(httpClient).getResponse();
Integer chainedResult = response
.map(this::actionThatTakesResponse)
.getOrElse(defaultChainedResult);
Stream<String> stream = response.toStream().map(it -> it.id);
// then
assertTrue(!stream.isEmpty());
assertTrue(response.isSuccess());
response.onSuccess(r -> assertEquals(id, r.id));
response.andThen(r -> assertEquals(id, r.id));
assertNotEquals(defaultChainedResult, chainedResult);
}
辅助方法 actionThatTakesResponse()
简单返回 id
的哈希值:
public int actionThatTakesResponse(Response response) {
return response.id.hashCode();
}
核心操作解析:
- 链式调用:
map()
仅在Success
时执行转换操作 - 默认值处理:
getOrElse()
在失败时返回默认值 - 状态检查:
isSuccess()
判断执行状态 - 副作用操作:
onSuccess()
:成功时执行 ConsumerandThen()
:成功时执行副作用操作(与onSuccess
类似)
- 流式转换:
toStream()
将Try
转为Stream
进行后续处理
💡 进阶技巧:使用
transform()
可直接操作Try
对象而无需解包:
public int actionThatTakesTryResponse(Try<Response> response, int defaultTransformation){
return response.transform(responses -> response.map(it -> it.id.hashCode())
.getOrElse(defaultTransformation));
}
3.2. 处理失败场景
当 httpClient
抛出异常时:
@Test
public void givenHttpClientFailure_whenMakeACall_shouldReturnFailure() {
// given
Integer defaultChainedResult = 1;
HttpClient httpClient = () -> {
throw new ClientException("problem");
};
// when
Try<Response> response = new VavrTry(httpClient).getResponse();
Integer chainedResult = response
.map(this::actionThatTakesResponse)
.getOrElse(defaultChainedResult);
Option<Response> optionalResponse = response.toOption();
// then
assertTrue(optionalResponse.isEmpty());
assertTrue(response.isFailure());
response.onFailure(ex -> assertTrue(ex instanceof ClientException));
assertEquals(defaultChainedResult, chainedResult);
}
失败场景关键操作:
- 状态判断:
isFailure()
返回true
- 异常处理:
onFailure()
执行异常回调 - 类型转换:
toOption()
将Failure
转为Option
:- ✅ 成功时:
Option
为Some
- ❌ 失败时:
Option
为None
- ✅ 成功时:
⚠️ 实用技巧:当需要将
Try
传递给只处理Option
的方法时,toOption()
特别有用。
3.3. 模式匹配实战
通过 recover()
结合模式匹配,可针对不同异常类型执行不同恢复策略:
@Test
public void givenHttpClientThatFailure_whenMakeACall_shouldReturnFailureAndNotRecover() {
// given
Response defaultResponse = new Response("b");
HttpClient httpClient = () -> {
throw new RuntimeException("critical problem");
};
// when
Try<Response> recovered = new VavrTry(httpClient).getResponse()
.recover(r -> Match(r).of(
Case(instanceOf(ClientException.class), defaultResponse)
));
// then
assertTrue(recovered.isFailure());
关键点:
- 仅当异常为
ClientException
时恢复为Success
- 其他异常(如
RuntimeException
)保持Failure
状态
💡 重新抛出关键异常:使用
getOrElseThrow()
可将关键异常向上抛出:
recovered.getOrElseThrow(throwable -> {
throw new RuntimeException(throwable);
});
对于可恢复异常,模式匹配能优雅处理多种异常类型:
@Test
public void givenHttpClientThatFailure_whenMakeACall_shouldReturnFailureAndRecover() {
// given
Response defaultResponse = new Response("b");
HttpClient httpClient = () -> {
throw new ClientException("non critical problem");
};
// when
Try<Response> recovered = new VavrTry(httpClient).getResponse()
.recover(r -> Match(r).of(
Case(instanceOf(ClientException.class), defaultResponse),
Case(instanceOf(IllegalArgumentException.class), defaultResponse)
));
// then
assertTrue(recovered.isSuccess());
}
恢复策略:
- ✅
ClientException
和IllegalArgumentException
被恢复为Success
- 其他异常保持
Failure
4. 总结
本文通过实战案例展示了 Vavr Try
容器的核心用法:
- 函数式异常处理:将异常处理融入正常流程
- 链式操作:通过
map
/recover
构建流畅 API - 状态显式化:编译期强制处理成功/失败两种状态
- 模式匹配:精准控制异常恢复策略
使用 Try
能显著提升代码的函数式特性和可读性,特别适合构建健壮的异步/流式处理管道。
完整示例代码见 GitHub 项目(Maven 项目,可直接导入运行)。