引言:异常到底该谁处理?
在日常 Java 业务开发中,我们常常写出这样的代码:
String result = someRemoteCall();
if (result == null) {
throw new RuntimeException("接口返回为空");
}
看起来没问题,但一旦这种模式重复几十次,并在不同的团队成员手中以不同的形式出现(Optional
、if-null-throw
、try-catch 包装、日志输出混杂),你就会发现:异常的处理并不统一,也很难组合。
有没有办法像处理数据一样“组合”异常策略?答案是:有 —— 结合 Function<T, R>
+ 策略链思想,我们可以优雅地构建一套“异常装饰器”工具类。
核心思想:Function 的包装链 + 异常策略分离
我们想要实现的目标是:
- 可以对任何 Function 进行统一的异常封装(比如异常转换、日志打点、返回默认值等)
- 保持函数式接口的组合能力(支持
.andThen()
/.compose()
) - 封装后的 Function 可被透明使用,不需要改业务代码
本质上是一种责任链模式 + 装饰器模式 + 函数式编程的融合。
工具类设计:FunctionChainWrapper
@FunctionalInterface
public interface CheckedFunction<T, R> {
R apply(T t) throws Exception;
}
我们定义了一个能抛异常的函数接口,然后封装一个 FunctionWrapper
工具:
public class FunctionChainWrapper<T, R> {
private final CheckedFunction<T, R> originalFunction;
private final List<Function<Throwable, R>> exceptionHandlers = new ArrayList<>();
private Function<Exception, RuntimeException> exceptionTranslator = RuntimeException::new;
private Consumer<Throwable> exceptionLogger = e -> {}; // 默认无操作
private R fallbackValue = null;
private boolean hasFallback = false;
public FunctionChainWrapper(CheckedFunction<T, R> originalFunction) {
this.originalFunction = originalFunction;
}
public FunctionChainWrapper<T, R> onException(Function<Throwable, R> handler) {
this.exceptionHandlers.add(handler);
return this;
}
public FunctionChainWrapper<T, R> logException(Consumer<Throwable> logger) {
this.exceptionLogger = logger;
return this;
}
public FunctionChainWrapper<T, R> translateException(Function<Exception, RuntimeException> translator) {
this.exceptionTranslator = translator;
return this;
}
public FunctionChainWrapper<T, R> fallback(R value) {
this.fallbackValue = value;
this.hasFallback = true;
return this;
}
public Function<T, R> build() {
return input -> {
try {
return originalFunction.apply(input);
} catch (Throwable ex) {
exceptionLogger.accept(ex);
for (Function<Throwable, R> handler : exceptionHandlers) {
try {
return handler.apply(ex);
} catch (Throwable ignore) {}
}
if (hasFallback) return fallbackValue;
if (ex instanceof RuntimeException) throw (RuntimeException) ex;
throw exceptionTranslator.apply(new Exception(ex));
}
};
}
}
使用示例:封装远程调用函数
FunctionChainWrapper<String, String> wrapper = new FunctionChainWrapper<>(input -> {
if ("bad".equals(input)) throw new IOException("网络失败");
return "Result: " + input;
});
Function<String, String> safeFunc = wrapper
.logException(e -> log.error("调用失败: {}", e.getMessage()))
.fallback("默认值")
.build();
String val = safeFunc.apply("bad"); // 输出:默认值
可以继续组合使用:
Function<String, String> func = safeFunc
.andThen(res -> res.toUpperCase())
.andThen(res -> res + " ✅");
String result = func.apply("bad"); // "默认值 ✅"
与 BiFunction 的扩展
只需简单改造:
@FunctionalInterface
public interface CheckedBiFunction<T, U, R> {
R apply(T t, U u) throws Exception;
}
public class BiFunctionChainWrapper<T, U, R> {
// 与 FunctionChainWrapper 类似,只是换成 BiFunction
}
你就可以封装两个参数的函数,如数据库查询、组合业务等:
BiFunctionChainWrapper<String, Integer, String> wrapper = new BiFunctionChainWrapper<>(
(name, age) -> {
if (age < 0) throw new IllegalArgumentException("非法年龄");
return name + " is " + age + " years old.";
}
);
BiFunction<String, Integer, String> safeFunc = wrapper
.translateException(e -> new BusinessException("年龄不合法", e))
.fallback("默认用户")
.build();
单元测试建议(Function 单测思路)
- 输入正常、输出正常
- 输入异常(会触发 fallback / log)
- 异常链中的处理函数也抛异常(验证链的容错)
- 验证 log 是否触发(可 mock
logger
) - 组合测试(andThen)是否正常传递
@Test
void testFallbackTriggered() {
FunctionChainWrapper<String, String> wrapper = new FunctionChainWrapper<>(s -> {
throw new RuntimeException("fail");
}).fallback("safe");
Function<String, String> safeFunc = wrapper.build();
Assertions.assertEquals("safe", safeFunc.apply("anything"));
}
避坑提示
- 不要用 try-catch 把所有逻辑包死,应该聚焦于“异常感知点”来封装(I/O、反序列化、远程调用等)
- 日志与 fallback 分离,不要耦合:打日志是一种副作用,fallback 是行为策略
- 组合时注意状态性函数(如带缓存)之间的顺序
延伸思考:函数式异常封装的更大价值
- 能让你的业务逻辑表达更加清晰(只关注 happy path)
- 异常处理逻辑被拆分并复用(通用 fallback、打点策略可组合)
- 可以在策略链中插入熔断器、缓存装饰器等 —— 用法无限扩展
总结
Function + 异常策略链
是一个十分实用且优雅的模式。它不仅解决了代码冗余和异常处理不一致的问题,还让我们重新认识了函数式接口的扩展能力。