写 Golang 的朋友应该经常接触这种代码:
在这种模式中,错误是值,调用方必须显式处理。
当然这里不是为了辩经这种错误模型与 try/catch 孰优孰劣。例如在读取文件时,文件不存在导致 IOException 是意料之外的问题,throw 出去就行,而使用 Apach POI 尝试读取 PDF 失败就可以视为 Error,调用方或许应当转而尝试 PDFBox。
许多框架在接口侧设计了 Result<T> 类型,也是起到类似的作用,然而一些框架在控制流中也使用这种类型,就场景语义来说是不合适的。
因此专门为框架设计了 R<T, E> 类型,提供一种函数式、安全、可组合的方式来表示操作结果,将把“可能失败”这件事提升为类型系统的一部分。
R<T, E> 是一个泛型结果容器,封装了成功值 (T value) 和失败值 (E error);
构造方法、常用方法与示例
如果需要从一个可能抛出异常的操作中获取结果,使用 R#of 方法将任何可能抛出异常的操作转化为安全的 R 对象。
判断状态:
map & flatMap
如果控制流中需要进行一系列可能会失败的操作,通常的写法可能会变成 if 金字塔:
函数式编程的朋友们这时也许会掏出 Monad,而 R 提供了 map 和 flatMap。
只对成功值生效,失败值保持不变,支持链式调用,用于组合复杂操作
需要链式操作多个可能失败的函数?使用 flatMap
失败值处理
提供默认值,或对错误信息进行包装
Source code on GitHub Gist
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package cc.ddrpa.labs.lang; | |
| import java.util.Optional; | |
| import java.util.function.Function; | |
| import java.util.function.Supplier; | |
| public record R<T, E>(T value, E error) { | |
| public static <T, E> R<T, E> ok(T value) { | |
| return new R<>(value, null); | |
| } | |
| public static <T, E> R<T, E> err(E error) { | |
| return new R<>(null, error); | |
| } | |
| /** | |
| * 执行给定的 supplier,并将结果包装为 Result。 | |
| * <p> | |
| * - 如果 supplier 成功返回值,则返回 Result.ok(value)。 | |
| * - 如果 supplier 抛出任何异常 (Throwable),则捕获异常并返回 Result.err(exception)。 | |
| * <p> | |
| * 这允许将可能抛异常的操作转化为安全的 Result,而无需调用方使用 try/catch。 | |
| * <p> | |
| * 示例: | |
| * <pre> | |
| * Result<Integer, Throwable> r1 = R.of(() -> 10 / 2); | |
| * // r1 = Result.ok(5) | |
| * | |
| * Result<Integer, Throwable> r2 = R.of(() -> 10 / 0); | |
| * // r2 = Result.err(ArithmeticException) | |
| * </pre> | |
| * | |
| * @param supplier 可能抛异常并返回 T 的操作 | |
| * @param <T> 成功值类型 | |
| * @return 如果 supplier 成功执行,返回 Result.ok(value);如果抛异常,返回 Result.err(exception) | |
| */ | |
| public static <T> R<T, Throwable> of(Supplier<T> supplier) { | |
| try { | |
| return R.ok(supplier.get()); | |
| } catch (Throwable e) { | |
| return R.err(e); | |
| } | |
| } | |
| public boolean isOk() { | |
| return error == null; | |
| } | |
| public boolean isErr() { | |
| return !isOk(); | |
| } | |
| public Optional<T> valueOpt() { | |
| return Optional.ofNullable(value); | |
| } | |
| public Optional<E> errorOpt() { | |
| return Optional.ofNullable(error); | |
| } | |
| /** | |
| * 将当前 Result 的成功值应用给定函数 f 转换为新的成功值。 | |
| * 如果当前 Result 是失败值,则保持失败不变。 | |
| * <p> | |
| * 这允许你对成功结果进行变换,同时保持失败信息不受影响。 | |
| * <p> | |
| * 示例: | |
| * <pre> | |
| * Result<Integer, String> r1 = Result.ok(10); | |
| * Result<Integer, String> r2 = r1.map(x -> x * 2); | |
| * // r2 = Result.ok(20) | |
| * | |
| * Result<Integer, String> r3 = Result.err("error"); | |
| * Result<Integer, String> r4 = r3.map(x -> x * 2); | |
| * // r4 = Result.err("error"),失败保持不变 | |
| * </pre> | |
| * | |
| * @param f 将成功值 T 转换为新值 U 的函数 | |
| * @param <U> 新成功值类型 | |
| * @return 转换后的 Result,如果当前 Result 失败则保持失败 | |
| */ | |
| public <U> R<U, E> map(Function<T, U> f) { | |
| return isOk() ? R.ok(f.apply(value)) : R.err(error); | |
| } | |
| /** | |
| * 如果当前 Result 表示成功值 (isOk() == true),则将其值传入给定的函数 f, | |
| * 并返回函数的结果。 | |
| * <p> | |
| * 如果当前 Result 表示失败值 (isErr() == true),则直接返回原失败,不会调用函数 f。 | |
| * <p> | |
| * 这允许你将多个可能失败的操作串联起来:每一步都可能返回失败,一旦遇到失败,后续操作不会执行。 | |
| * <p> | |
| * 示例: | |
| * <pre> | |
| * Result<Integer, String> r1 = Result.ok(10); | |
| * Result<Integer, String> r2 = r1.flatMap(x -> Result.ok(x * 2)); | |
| * // r2 = Result.ok(20) | |
| * | |
| * Result<Integer, String> r3 = Result.err("error"); | |
| * Result<Integer, String> r4 = r3.flatMap(x -> Result.ok(x * 2)); | |
| * // r4 = Result.err("error"),lambda 不会执行 | |
| * </pre> | |
| * | |
| * @param f 将成功值 T 转换为另一个 Result<U, E> 的函数 | |
| * @param <U> 新的成功值类型 | |
| * @return 如果当前 Result 成功,返回 f.apply(value) 的结果;如果当前失败,返回原失败 | |
| */ | |
| public <U> R<U, E> flatMap(Function<T, R<U, E>> f) { | |
| return isOk() ? f.apply(value) : R.err(error); | |
| } | |
| /** | |
| * 如果当前 Result 表示失败值 (isErr() == true),则使用给定函数 f 将失败值转换为成功值。 | |
| * 如果当前 Result 已经是成功值,则保持不变。 | |
| * <p> | |
| * 这允许你在失败时提供一个“降级值”或者默认值。 | |
| * <p> | |
| * 示例: | |
| * <pre> | |
| * Result<Integer, String> r1 = Result.err("error"); | |
| * Result<Integer, String> r2 = r1.recover(e -> 0); | |
| * // r2 = Result.ok(0) | |
| * | |
| * Result<Integer, String> r3 = Result.ok(10); | |
| * Result<Integer, String> r4 = r3.recover(e -> 0); | |
| * // r4 = Result.ok(10) ,原成功值保持不变 | |
| * </pre> | |
| * | |
| * @param f 将失败值 E 转换为成功值 T 的函数 | |
| * @return 如果当前 Result 成功,返回自身;如果失败,返回通过 f 转换后的成功 Result | |
| */ | |
| public R<T, E> recover(Function<E, T> f) { | |
| return isOk() ? this : R.ok(f.apply(error)); | |
| } | |
| /** | |
| * 将当前 Result 的失败值应用给定函数 f 转换为新的失败值。 | |
| * 如果当前 Result 是成功值,则保持成功不变。 | |
| * <p> | |
| * 这允许你在传播失败时对错误信息进行调整或包装,而不影响成功结果。 | |
| * <p> | |
| * 示例: | |
| * <pre> | |
| * Result<Integer, String> r1 = Result.err("error"); | |
| * Result<Integer, Integer> r2 = r1.mapError(e -> e.length()); | |
| * // r2 = Result.err(5) | |
| * | |
| * Result<Integer, String> r3 = Result.ok(10); | |
| * Result<Integer, Integer> r4 = r3.mapError(e -> e.length()); | |
| * // r4 = Result.ok(10) ,成功保持不变 | |
| * </pre> | |
| * | |
| * @param f 将失败值 E 转换为新失败值 F 的函数 | |
| * @param <F> 新失败值类型 | |
| * @return 转换后的 Result,如果当前 Result 成功则保持成功 | |
| */ | |
| public <F> R<T, F> mapError(Function<E, F> f) { | |
| return isOk() ? R.ok(value) : R.err(f.apply(error)); | |
| } | |
| } |