回避 javac "ambiguous" 对仅参数 lambda 是 void 还是 non-void 不同的重载方法的警告

Sidestep the javac "ambiguous" warning for overloaded methods that only differ by whether parameter lambda is void or non-void

我正在使用以下方法(以及其他方法)编写实用程序 class:

 class FunctionNamingUtils {
    public static <T> Consumer<T> named(String name, Consumer<T> delegate) {
        class NamedConsumer implements Consumer<T> {
            @Override public void accept(T t) { delegate.accept(t); }
            @Override public String toString() { return name; }
        }
        return new NamedConsumer();
    }

    public static <T, R> Function<T, R> named(String name, Function<T, R> delegate) {
        class NamedFunction implements Function<T, R> {
            @Override public R apply(T t) { return delegate.apply(t); }
            @Override public String toString() { return name; }
        }
        return new NamedFunction();
    }
}

编译器抱怨:

Warning: java: named(java.lang.String,java.util.function.Consumer) in ... is potentially ambiguous with named(java.lang.String,java.util.function.Function) in ...

我确实明白警告的目的 - 根据 lambda returns 是一个值还是 void 我们将采用一种方式或另一种方式,并且对于很难看到的单语句 lambda。

问题是,在这种情况下,这正是我们想要的,我想使用重载来减少必须记住相同功能的两个方法名称的认知负担(我已经不得不这样做与 namedPredicate 妥协,这与布尔函数冲突)。

我正在寻找任何想法 - 关于抑制警告或以不同方式表达 API。重点是客户端的清晰度和易用性。

我只关心Java8+。

其实我对 lambda 的理解是不完整的。事实证明,任何返回值的 lambda 都可以合法地用于 void lambda 的位置。

引用自errorprone/FunctionalInterfaceClash

JLS 15.12.2.1 says that lambdas whose body is a statement expression are compatible with functional interfaces whose function type is void-returning or value returning:

  • A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:

  • The arity of the target type’s function type is the same as the arity of the lambda expression. If the target type’s function type has a void return, then the lambda body is either a statement expression

  • (§14.8) or a void-compatible block (§15.27.2). If the target type’s function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2).

换句话说,考虑到上面的实现,传递一个消费者和一个功能性的 lambda 会很好地解决,但是有一个用例,我们想要使用实际上 returns一个值。

例如这两个会失败:

Functions.named(name, Objects::requireNonNull);
Functions.named(name, it -> Objects.requireNonNull(it));

为了让它工作,我们需要像这样添加显式语句块:

Functions.named(name, it -> { Objects.requireNonNull(it); });

虽然这是一个边缘案例,我可以在 API 中记录它,但这改变了可读性权衡,所以现在我将方法重命名为:

Functions.fun(name, delegate)
Functions.con(name, delegate)
Functions.pre(name, delegate)
Functions.sup(name, delegate)

虽然没有 named 好,但它仍然可读且一致。

我简要考虑了 namedFunction 样式名称,但考虑到这些名称在代码库中的使用频率,我宁愿让它们更简洁。