Java方法引用的简洁组合方式?

11

给定一些Java 8方法函数:

class Foo { Bar getBar() {} }
class Bar { Baz getBaz() {} }

两个访问器组合起来的样子如下:

Function<Foo, Bar> getBarFromFoo = Foo::getBar;
Function<Bar, Baz> getBazFromBar = Bar::getBaz;
Function<Foo, Baz> getBazFromFoo = getBarFromFoo.andThen(getBazFromBar);

有更简洁的方法吗?这似乎有效。

((Function<Foo, Bar>) Foo::getBar).andThen(Bar::getBaz)

但它相当丑陋。外部括号由于优先级原因是有意义的,但为什么需要强制转换呢?

(Foo::getBar::getBaz会很好,但不幸的是...)


为什么不这样写 foo -> foo.getBar()::getBaz?看起来你把它搞复杂了。我有什么地方理解错了吗? - Dioxin
2
@VinceEmigh,你是不是想说 foo -> foo.getBar().getBaz()?否则的话,这样写就没有意义了。 - Andrew Tobilko
@VinceEmigh,你提出的方式不够灵活。最好是在运行时准备简单的映射器(a->bb->c),并用简单的操作如FunctioncomposeandThen来组合它们,而不是在编译时建立所有可能的情况。 - Andrew Tobilko
@AndrewTobilko 如果灵活性是一个问题,您可以使用柯里化:foo -> bar -> bar::getBaz - Dioxin
@VinceEmigh,你不能使用 foo -> bar -> bar::getBaz 来实现一个映射序列(在这里,获取一个 Baz 作为输出)。它可能会返回类似于 Function<Foo, Function<Bar, Function<Bar, Baz>>> 的东西,这是没有意义的。 - Andrew Tobilko
5个回答

10

让我们定义一个函数式接口:

@FunctionalInterface
interface MyFunctionalInterface {
    Bar getBar(Foo f);
}

我们可以稍微简化方法引用Foo::getBar的写法,

(Foo foo) -> foo.getBar();

这意味着"拿一个Foo并返回一个Bar"。对于该描述,有很多方法可行(例如我们的接口与getBar和一个Function<Foo, Bar>及其apply):

MyFunctionalInterface f1 = (Foo foo) -> foo.getBar();
Function<Foo, Bar> f2 = (Foo foo) -> foo.getBar();

这就是为什么需要演员阵容的答案。


为了肯定地回答是否存在更简洁的方法,我们必须设定一个上下文。这个上下文无疑给了我们一个函数可以继续使用:

class Functions {
    public static <I, O> Function<I, O> of(Function<I, O> function) {
        return function;
    }
}

Functions.of(Foo::getBar).andThen(Bar::getBaz);

3

除了使用andThen()之外,Java中没有专门的组合函数的方法。

你需要进行强制转换,因为Foo::getBar是有歧义的。它可以匹配具有类似方法签名的每个接口。

不幸的是,((Function<Foo, Bar>) Foo::getBar).andThen(Bar::getBaz)是您能做到的最好的方法。


1
也许只需要使用一个lambda表达式?
x -> x.getBar().getBaz()

由于类型模糊,除了您已经建议的方式外,没有其他方法来组合函数。这甚至比Foo::getBar::getBaz还要短。


0

使用 Guava,您可以使用 Functions.compose

import com.google.common.base.Functions;

...

Functions.compose(Bar::getBaz, Foo::getBar);

0

这是你能得到的最好结果。如果你认为这会起作用:

Foo::getBar::getBaz

不会的。这是因为Foo::getBar是一个多态表达式 - 它取决于使用的上下文 - 它可以是一个Function,但也可以是一个Predicate,因此它可能适用于许多事物,所以强制转换在那里是必要的。

您可以将其隐藏在一个方法后面,该方法将执行链接和andThen,但问题仍然存在。

编辑

在此处查看示例:

public static void cool(Predicate<Integer> predicate) {

}

public static void cool(Function<Integer, String> function) {

}

而且表达式 cool(i -> "Test"); 将无法编译通过


谢谢。我知道这行不通,只是希望...类型推理器应该知道它不是一个谓词,因为它不返回布尔值。虽然我不是Java类型推理巫师,但似乎逻辑上讲,由于Foo::getBar满足任何形式为Foo->Bar的函数接口,而getBaz满足 Bar->Baz,那么Foo::getBar::getBaz可以是一个多态表达式,可以满足任何形式为Foo->Baz的函数接口。 - Gene
@Gene,“Predicate”和“Function”只是一个示例,证明它们确实是多元表达式(请参见编辑内容)...对于lambda表达式和方法引用的“类型推断”并不那么明显;这可能是为什么这种链式调用根本没有被实现的原因-它远非易事。 - Eugene
我实际上写过一个类型推断器。我知道这并不容易。但这并不意味着它是不可能的。 - Gene
@Gene 我并不是说这是不可能的,未来的某个版本可能会有所改进...只是目前而言,就我个人而言(我绝对不是做这个的合适人选),无法找到一种真正安全的方法来实现它。 - Eugene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接