R 类型

2026-01-29, 星期四, 17:08

DevJava

写 Golang 的朋友应该经常接触这种代码:

f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}
// do something with the open *File f

在这种模式中,错误是值,调用方必须显式处理。

当然这里不是为了辩经这种错误模型与 try/catch 孰优孰劣。例如在读取文件时,文件不存在导致 IOException 是意料之外的问题,throw 出去就行,而使用 Apach POI 尝试读取 PDF 失败就可以视为 Error,调用方或许应当转而尝试 PDFBox。

许多框架在接口侧设计了 Result<T> 类型,也是起到类似的作用,然而一些框架在控制流中也使用这种类型,就场景语义来说是不合适的。

因此专门为框架设计了 R<T, E> 类型,提供一种函数式、安全、可组合的方式来表示操作结果,将把“可能失败”这件事提升为类型系统的一部分。

R<T, E> 是一个泛型结果容器,封装了成功值 (T value) 和失败值 (E error);

构造方法、常用方法与示例

// 成功
R<Integer, String> r1 = R.ok(10);
// 失败
R<Integer, String> r2 = R.err("失败信息");
R<Integer, String> r3 = R.err(new RuntimeException("Oops"));

如果需要从一个可能抛出异常的操作中获取结果,使用 R#of 方法将任何可能抛出异常的操作转化为安全的 R 对象。

R<Integer, Throwable> r3 = R.of(() -> 10 / 2);
// ok(5)

R<Integer, Throwable> r4 = R.of(() -> 10 / 0);
// err(ArithmeticException)

判断状态:

if (r1.isOk()) {
    logger.info(r1.value());
} else {
    logger.error("操作失败", r1.error());
}

map & flatMap

如果控制流中需要进行一系列可能会失败的操作,通常的写法可能会变成 if 金字塔:

if (ok1) {
    if (ok2) {
        if (ok3) {
        }
    }
}

函数式编程的朋友们这时也许会掏出 Monad,而 R 提供了 mapflatMap

R<Integer, String> doubled = r1.map(x -> x * 2);

只对成功值生效,失败值保持不变,支持链式调用,用于组合复杂操作

R<Integer, String> r1 = R.ok(5)
    .map(x -> x + 10)
    .map(x -> x * 2);
// r1 = ok(30)

R<Integer, String> r2 = R.err("Oops")
    .map(x -> x + 10)
    .map(x -> x * 2);
// r2 = err("Oops")

需要链式操作多个可能失败的函数?使用 flatMap

R<Integer, String> r1 = R.ok(11)
    .flatMap(x -> x > 10 ? R.ok(x - 5) : R.err("小于阈值"))
    .flatMap(x -> R.ok(x * 2));
// r1 = ok(12)

R<Integer, String> r2 = R.ok(9)
    .flatMap(x -> x > 10 ? R.ok(x - 5) : R.err("小于阈值"))
    .flatMap(x -> R.ok(x * 2));
// r2 = err("小于阈值")

失败值处理

提供默认值,或对错误信息进行包装

R<Integer, String> recovered = R.err("失败").recover(e -> 0);
// ok(0)

R<Integer, Integer> mappedError = R.err("Oops").mapError(String::length);
// err(4)
// 虽然不知道 length of error message 有什么用就是了

Source code on GitHub Gist

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));
}
}
view raw R.java hosted with ❤ by GitHub