function本质是类型安全的映射函数,具泛型约束、可实例化传递组合,核心为无副作用的apply(t)→r纯转换。

Function 的本质就是类型安全的映射函数
它不是语法糖,也不是装饰器,就是一个带泛型约束的、可实例化、可传递、可组合的函数对象。核心就一条:apply(T) 方法把 T 变成 R,不抛异常、不改状态、不依赖外部变量——这决定了它天然适合做纯转换逻辑。
常见错误是把它当普通方法写,比如在 apply 里查数据库、发 HTTP 请求、修改全局变量,这会破坏函数式语义,导致流操作(map)中途失败或结果不可复现。
- 必须确保
apply是无副作用的:只读输入、只返输出 - 泛型不能用原始类型:写
Function<int string></int>会编译报错,得用Integer - 空值处理要显式:
apply(null)默认不校验,如果业务不允许 null 输入,得自己加Objects.requireNonNull(t)
怎么写一个真正可用的 Function 实例
别用匿名内部类,直接用 lambda 或方法引用。关键看输入输出类型是否对齐,以及是否满足业务契约。
例如把字符串转为非负整数,同时拒绝非法格式:
Function<string integer> safeParseInt = s -> {
if (s == null || s.trim().isEmpty()) {
throw new IllegalArgumentException("input cannot be null or blank");
}
try {
int i = Integer.parseInt(s.trim());
return i
<ul>
<li>lambda 主体必须有明确返回值,且类型匹配声明的 <code>R</code>
</li>
<li>不要在 lambda 里捕获并吞掉异常;<code>Function</code> 不声明受检异常,所以只能抛运行时异常</li>
<li>如果只是简单调用已有方法,优先用方法引用:<code>String::length</code> 比 <code>s -> s.length()</code> 更简洁安全</li>
</ul>
<h3>andThen 和 compose 怎么选才不绕晕</h3>
<p>两者都用于函数组合,但执行顺序相反,容易混淆。记住口诀:<code>andThen</code> 是“先我后你”,<code>compose</code> 是“先你后我”。</p>
<p>比如想实现 “字符串 → trim → toUpperCase → length”:</p>
<pre class="brush:java;toolbar:false;">Function<string string> trim = String::trim;
Function<string string> upper = String::toUpperCase;
Function<string integer> len = String::length;
// 正确链式:trim → upper → len
Function<string integer> pipeline = trim.andThen(upper).andThen(len);
// 等价写法(compose 版本):
Function<string integer> pipeline2 = len.compose(upper).compose(trim);</string></string></string></string></string>
-
f.andThen(g)等价于数学上的g(f(x)) -
f.compose(g)等价于数学上的f(g(x)) - 实际编码中,按业务流程从左到右写更自然,所以
andThen使用频率远高于compose
Function 当参数传给方法时要注意什么
这是最易出错的场景:接收方没做 null 检查,调用方传了 null,运行时报 NullPointerException 却不知道哪一层崩的。
- 方法签名里明确标注参数是否允许 null,比如:
public void process(Function<string integer> parser)</string>应补充 Javadoc 说明 parser 不可为 null - 调用方若不确定来源是否可靠,应包装一层防御逻辑:
Objects.requireNonNull(parser, "parser must not be null") - 避免在方法内部多次调用
apply却不做缓存——如果apply开销大(如解析 JSON),应提前计算并复用结果
真正难的从来不是写对一个 Function,而是让它的生命周期、空值边界、异常语义和上下游协同起来。多数 bug 都藏在组合调用的缝隙里,而不是单个 lambda 表达式中。










